面试必知:最全手写JS题解析与实战指南
2025.09.19 12:47浏览量:1简介:本文汇总了面试中最常见的手写JS题,涵盖数据类型、函数、原型链、异步处理等核心知识点,通过详细解析和代码示例帮助开发者系统掌握手写JS题的解题思路,提升面试通过率。
面试中最全的手写JS题解析
在前端开发面试中,手写JavaScript代码题是考察候选人基础能力的核心环节。这类题目不仅能检验开发者对语言特性的理解,还能体现其编码规范和问题拆解能力。本文将系统梳理面试中最常出现的手写JS题,从基础到进阶逐层解析,帮助读者构建完整的知识体系。
一、数据类型与基础操作
1. 类型判断:实现typeof的增强版
原生typeof存在局限性(如无法区分Array和Object),面试常要求实现更精准的类型判断函数。
function getType(value) {const typeStr = Object.prototype.toString.call(value);return typeStr.match(/\[object (.*?)\]/)[1].toLowerCase();}// 测试用例getType([]); // "array"getType(null); // "null"getType(/regex/); // "regexp"
关键点:
- 使用
Object.prototype.toString获取标准类型标识 - 通过正则提取实际类型名称
- 覆盖所有原始类型和引用类型
2. 深拷贝实现
深拷贝是面试高频题,需处理循环引用、特殊对象(如Date、RegExp)等边界情况。
function deepClone(obj, hash = new WeakMap()) {if (obj === null || typeof obj !== 'object') return obj;// 处理循环引用if (hash.has(obj)) return hash.get(obj);let clone;if (obj instanceof Date) clone = new Date(obj);else if (obj instanceof RegExp) clone = new RegExp(obj);else {clone = Array.isArray(obj) ? [] : {};hash.set(obj, clone);for (let key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key], hash);}}}return clone;}
优化方向:
- 使用
WeakMap避免内存泄漏 - 特殊对象单独处理
- 递归实现时注意栈溢出风险
二、函数与高阶编程
1. 柯里化(Currying)实现
柯里化要求将多参数函数转换为单参数函数的嵌套调用。
function curry(fn) {return function curried(...args) {if (args.length >= fn.length) {return fn.apply(this, args);} else {return function(...args2) {return curried.apply(this, args.concat(args2));}}}}// 使用示例const sum = curry((a, b, c) => a + b + c);sum(1)(2)(3); // 6
应用场景:
- 参数复用
- 延迟执行
- 函数组合
2. 防抖与节流
这两个函数是性能优化的经典手段,需清晰区分实现差异。
// 防抖:事件触发后等待n秒再执行function debounce(fn, delay) {let timer;return function(...args) {clearTimeout(timer);timer = setTimeout(() => fn.apply(this, args), delay);}}// 节流:固定时间间隔执行一次function throttle(fn, interval) {let lastTime = 0;return function(...args) {const now = Date.now();if (now - lastTime >= interval) {fn.apply(this, args);lastTime = now;}}}
选择依据:
- 防抖适合输入框联想等场景
- 节流适合滚动事件、鼠标移动等高频事件
三、原型链与继承
1. 实现new操作符
需理解构造函数执行过程:创建对象、关联原型、执行构造方法、返回对象。
function myNew(constructor, ...args) {const obj = Object.create(constructor.prototype);const result = constructor.apply(obj, args);return result instanceof Object ? result : obj;}// 测试function Person(name) {this.name = name;}const p = myNew(Person, 'Tom');console.log(p.name); // "Tom"
执行步骤:
- 创建空对象并关联原型
- 调用构造函数,绑定
this - 处理构造函数返回值
2. 多种继承方式实现
需掌握原型链继承、构造函数继承、组合继承等模式的差异。
// 组合继承示例function Parent(name) {this.name = name;this.colors = ['red'];}Parent.prototype.sayName = function() {console.log(this.name);}function Child(name, age) {Parent.call(this, name); // 构造函数继承this.age = age;}Child.prototype = new Parent(); // 原型链继承Child.prototype.constructor = Child;const child1 = new Child('Tom', 18);child1.colors.push('blue');console.log(child1.colors); // ["red", "blue"]const child2 = new Child('Jerry', 20);console.log(child2.colors); // ["red"]
模式对比:
| 继承方式 | 优点 | 缺点 |
|————————|—————————————|—————————————|
| 原型链继承 | 实现简单 | 共享引用属性 |
| 构造函数继承 | 可传参、不共享属性 | 无法继承原型方法 |
| 组合继承 | 结合两者优点 | 调用两次父构造函数 |
四、异步编程
1. 实现Promise
需完整实现then的链式调用、状态变更、异步决议等特性。
class MyPromise {constructor(executor) {this.state = 'pending';this.value = undefined;this.reason = undefined;this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];const resolve = (value) => {if (this.state === 'pending') {this.state = 'fulfilled';this.value = value;this.onFulfilledCallbacks.forEach(fn => fn());}}const reject = (reason) => {if (this.state === 'pending') {this.state = 'rejected';this.reason = reason;this.onRejectedCallbacks.forEach(fn => fn());}}try {executor(resolve, reject);} catch (err) {reject(err);}}then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };const promise2 = new MyPromise((resolve, reject) => {if (this.state === 'fulfilled') {setTimeout(() => {try {const x = onFulfilled(this.value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);} else if (this.state === 'rejected') {setTimeout(() => {try {const x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);} else {this.onFulfilledCallbacks.push(() => {setTimeout(() => {try {const x = onFulfilled(this.value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);});this.onRejectedCallbacks.push(() => {setTimeout(() => {try {const x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);});}});return promise2;}}// 辅助函数:处理thenable对象和循环引用function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) return reject(new TypeError('Chaining cycle detected'));let called = false;if (x !== null && (typeof x === 'object' || typeof x === 'function')) {try {const then = x.then;if (typeof then === 'function') {then.call(x,y => {if (called) return;called = true;resolvePromise(promise2, y, resolve, reject);},r => {if (called) return;called = true;reject(r);});} else {resolve(x);}} catch (e) {if (called) return;called = true;reject(e);}} else {resolve(x);}}
实现要点:
- 三种状态管理
- 异步执行回调
- 链式调用支持
- 循环引用检测
2. 实现Async/Await
基于Generator函数实现简易版Async/Await。
function asyncToGenerator(generatorFn) {return function(...args) {const gen = generatorFn.apply(this, args);return new Promise((resolve, reject) => {function step(key, arg) {let info;try {info = gen[key](arg);} catch (error) {return reject(error);}const { value, done } = info;if (done) {return resolve(value);}Promise.resolve(value).then(v => step('next', v),e => step('throw', e));}step('next');});}}// 使用示例const asyncFunc = asyncToGenerator(function* () {const a = yield 1;const b = yield a + 2;return b + 3;});asyncFunc().then(console.log); // 6
工作原理:
- 将Generator函数包装为Promise
- 通过
step函数控制执行流程 - 自动处理
yield表达式的异步决议
五、算法与数据结构
1. 实现扁平化数组
需处理多级嵌套和类型判断。
function flatten(arr) {let result = [];for (let item of arr) {if (Array.isArray(item)) {result = result.concat(flatten(item));} else {result.push(item);}}return result;}// 优化版(支持深度参数)function flattenDeep(arr, depth = 1) {return depth > 0? arr.reduce((prev, cur) =>prev.concat(Array.isArray(cur) ? flattenDeep(cur, depth - 1) : cur),[]): arr.slice();}
应用场景:
- 处理API返回的嵌套数据
- 数据预处理
2. 实现LRU缓存
需结合Map数据结构和双向链表实现高效操作。
class LRUCache {constructor(capacity) {this.capacity = capacity;this.cache = new Map();}get(key) {if (this.cache.has(key)) {const value = this.cache.get(key);this.cache.delete(key);this.cache.set(key, value); // 重新插入实现最近使用return value;}return -1;}put(key, value) {if (this.cache.has(key)) {this.cache.delete(key);} else if (this.cache.size >= this.capacity) {const oldestKey = this.cache.keys().next().value;this.cache.delete(oldestKey);}this.cache.set(key, value);}}
性能分析:
get和put操作时间复杂度均为O(1)- 利用Map保持插入顺序的特性
六、备考建议
- 系统梳理知识体系:按数据类型、函数、异步、算法等维度分类整理
- 刻意练习:每天手写3-5道典型题,注重代码规范和边界处理
- 理解原理:不仅要会写,更要明白为什么这样实现
- 模拟面试:找同伴进行模拟面试,训练表达能力和临场反应
- 查看源码:研究Lodash、Axios等库的实现,学习最佳实践
通过系统准备和针对性练习,开发者可以显著提升手写JS题的解题能力,在面试中展现出扎实的基本功和良好的编码素养。记住,面试官考察的不仅是代码的正确性,更是解决问题的思路和工程化思维。

发表评论
登录后可评论,请前往 登录 或 注册