前端面试手写代码高频题库:2026年精选汇总

手写代码是前端面试的重要环节,不仅能考察JavaScript基础功底,更能体现编程思维和代码能力。本文整理了2026年前端面试中最常见的手写题目,助你备战金三银四。

在当今的前端面试中,手写代码环节几乎成为必考项。面试官通过现场编码,考察求职者对JavaScript核心概念的掌握程度、代码实现能力和问题解决思路。本文将根据最新的面试趋势,为大家分类整理前端高频手写题。

一、JavaScript基础篇

1. 类型判断与原型相关

手写instanceof:判断构造函数的prototype是否出现在实例的原型链上。

复制代码
function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left);
  let prototype = right.prototype;
  
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
}

手写new操作符:创建实例对象,链接原型,绑定this,执行构造函数。

复制代码
function myNew(constructor, ...args) {
  // 1. 创建新对象,链接到构造函数原型
  const obj = Object.create(constructor.prototype);
  // 2. 执行构造函数,绑定this
  const result = constructor.apply(obj, args);
  // 3. 如果构造函数返回对象,则返回该对象,否则返回新创建的对象
  return result instanceof Object ? result : obj;
}

2. 函数与方法实现

手写call/apply/bind:改变函数执行时的this指向。

复制代码
// call实现
Function.prototype.myCall = function(context, ...args) {
  context = context || window;
  const fn = Symbol();
  context[fn] = this;
  const result = context[fn](...args);
  delete context[fn];
  return result;
};

// bind实现(考虑new操作符的情况)
Function.prototype.myBind = function(context, ...args) {
  const _this = this;
  return function F(...args2) {
    if (this instanceof F) {
      return new _this(...args, ...args2);
    }
    return _this.apply(context, args.concat(args2));
  };
};

函数柯里化:将多参数函数转换为一系列单参数函数。

复制代码
function curry(fn, len = fn.length) {
  return function _curry(...args) {
    if (args.length >= len) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return _curry.apply(this, args.concat(args2));
      };
    }
  };
}

二、数组操作篇

1. 数组去重多种方案

复制代码
// 方法1:Set(最简洁)
function unique(arr) {
  return [...new Set(arr)];
}

// 方法2:filter + indexOf
function unique2(arr) {
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

// 方法3:reduce(可处理复杂数据类型)
function unique3(arr) {
  return arr.reduce((acc, cur) => {
    return acc.includes(cur) ? acc : [...acc, cur];
  }, []);
}

2. 数组扁平化

复制代码
// 递归reduce实现
function flatten(arr) {
  return arr.reduce((acc, cur) => {
    return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, []);
}

// 迭代实现(避免递归栈溢出)
function flatten2(arr) {
  while (arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
}

// 使用ES2019 flat方法
function flatten3(arr) {
  return arr.flat(Infinity);
}

3. 数组与树结构转换

扁平数组转树形结构(常见于菜单、目录等场景):

复制代码
function arrayToTree(arr) {
  const map = {};
  const tree = [];
  
  // 建立id到节点的映射
  arr.forEach(item => {
    map[item.id] = { ...item, children: [] };
  });
  
  // 建立父子关系
  arr.forEach(item => {
    if (item.parentId && map[item.parentId]) {
      map[item.parentId].children.push(map[item.id]);
    } else {
      tree.push(map[item.id]);
    }
  });
  
  return tree;
}

三、异步编程篇

1. Promise实现

Promise是异步编程的核心,手写Promise能深刻理解异步机制。

复制代码
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 (error) {
      reject(error);
    }
  }
  
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
    
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'FULFILLED') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      } else if (this.state === 'REJECTED') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      } else {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });
        
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });
      }
    });
    
    return promise2;
  }
  
  resolvePromise(promise2, x, resolve, reject) {
    // Promise解决过程,处理thenable对象和循环引用
    if (promise2 === x) {
      return reject(new TypeError('Chaining cycle detected for promise'));
    }
    
    if (x instanceof MyPromise) {
      x.then(resolve, reject);
    } else {
      resolve(x);
    }
  }
  
  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      let count = 0;
      
      promises.forEach((promise, index) => {
        MyPromise.resolve(promise).then(value => {
          results[index] = value;
          count++;
          if (count === promises.length) {
            resolve(results);
          }
        }, reject);
      });
    });
  }
  
  static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.forEach(promise => {
        MyPromise.resolve(promise).then(resolve, reject);
      });
    });
  }
}

2. 异步控制函数

防抖(Debounce):连续触发时,只在最后执行一次。

复制代码
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

节流(Throttle):连续触发时,在一定时间间隔内只执行一次。

复制代码
// 时间戳版本(立即执行)
function throttle(fn, delay) {
  let start = Date.now();
  return function(...args) {
    const now = Date.now();
    if (now - start >= delay) {
      fn.apply(this, args);
      start = now;
    }
  };
}

// 定时器版本(延迟执行)
function throttle2(fn, delay) {
  let timer = null;
  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

四、数据处理篇

1. 深拷贝

深拷贝需要考虑循环引用、特殊对象类型等多种边界情况。

复制代码
function deepClone(obj, hash = new WeakMap()) {
  // 基本数据类型直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理日期对象
  if (obj instanceof Date) {
    return new Date(obj);
  }
  
  // 处理正则表达式
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
  
  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  // 创建新对象/数组
  const cloneObj = Array.isArray(obj) ? [] : {};
  hash.set(obj, cloneObj);
  
  // 递归拷贝属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  
  return cloneObj;
}

2. 对象创建与继承

手写Object.create:使用现有对象作为新对象的原型。

复制代码
function create(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}

五、实用功能篇

1. URL参数解析

复制代码
function parseUrl(url) {
  const params = {};
  const urlObj = new URL(url);
  
  urlObj.searchParams.forEach((value, key) => {
    if (params[key] !== undefined) {
      params[key] = [].concat(params[key], value);
    } else {
      params[key] = value;
    }
  });
  
  return params;
}

2. 模板引擎简单实现

复制代码
function templateEngine(template, data) {
  return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
    return data[key] !== undefined ? data[key] : match;
  });
}

3. 千分位格式化

复制代码
function formatNumber(num) {
  const [integer, decimal] = num.toString().split('.');
  const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  return decimal ? `${formattedInteger}.${decimal}` : formattedInteger;
}

六、算法与数据结构篇

1. 排序算法

快速排序:分治思想,选择基准元素进行分区。

复制代码
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  
  const pivotIndex = Math.floor(arr.length / 2);
  const pivot = arr.splice(pivotIndex, 1)[0];
  const left = [];
  const right = [];
  
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  
  return quickSort(left).concat([pivot], quickSort(right));
}

2. 括号匹配检查

复制代码
function isBalanced(expression) {
  const stack = [];
  const brackets = { '(': ')', '[': ']', '{': '}' };
  
  for (let char of expression) {
    if (brackets[char]) {
      stack.push(char);
    } else if (Object.values(brackets).includes(char)) {
      if (stack.length === 0 || brackets[stack.pop()] !== char) {
        return false;
      }
    }
  }
  
  return stack.length === 0;
}

备考建议与面试技巧

  1. 理解原理优于死记硬背:掌握每个手写题背后的设计思想和应用场景

  2. 注重代码健壮性:考虑边界情况、异常处理和性能优化

  3. 熟练使用ES6+特性:如解构赋值、扩展运算符、箭头函数等

  4. 多做实际练习:在IDE中实际编写、调试代码,而不仅仅是阅读

  5. 准备时间与空间复杂度分析:能够分析自己编写算法的时间/空间复杂度

手写代码能力需要长期积累和实践,建议在日常开发中多思考底层实现,遇到工具函数时尝试自己实现一遍,从而加深对JavaScript语言特性和设计模式的理解。

面试小技巧:在编写代码时,可以边写边解释自己的思路,展现解决问题的思考过程,这比单纯写出正确答案更能体现技术深度。


2026前端面试手写代码高频题库:从基础到框架原理全面解析

手写代码是前端面试的必考环节,直接考察开发者对JavaScript核心原理的掌握深度。本文整理了2026年大厂前端面试中最常见的手写题目,涵盖JavaScript基础、异步编程、框架原理等8大类别,每个题目都提供可运行代码和详细解析。

一、JavaScript基础篇

1. new操作符实现

复制代码
// 完整实现
function myNew(constructor, ...args) {
  // 1. 创建空对象并链接原型
  const obj = Object.create(constructor.prototype);
  
  // 2. 绑定this执行构造函数
  const result = constructor.apply(obj, args);
  
  // 3. 处理构造函数返回值
  return result instanceof Object ? result : obj;
}

// 使用示例
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function() {
  console.log(`我叫${this.name}`);
};

const p = myNew(Person, '小明', 20);
p.say(); // 输出: 我叫小明
console.log(p.age); // 输出: 20

核心要点:

  • 创建新对象,并将原型指向构造函数的prototype

  • 执行构造函数,将this绑定到新对象

  • 如果构造函数返回对象,则返回该对象,否则返回新对象

2. instanceof实现

复制代码
// 手写instanceof
function myInstanceof(obj, constructor) {
  // 基本类型直接返回false
  if (obj === null || typeof obj !== 'object') {
    return false;
  }
  
  // 获取对象的原型
  let proto = Object.getPrototypeOf(obj);
  
  // 沿着原型链向上查找
  while (proto !== null) {
    if (proto === constructor.prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto);
  }
  
  return false;
}

// 测试用例
console.log(myInstanceof([], Array));    // true
console.log(myInstanceof({}, Object));   // true
console.log(myInstanceof(123, Number));  // false (基本类型)

原理分析:

instanceof检查构造函数的prototype是否出现在对象的原型链上。通过while循环不断向上查找原型,直到找到匹配的原型或到达原型链末端(null)。

3. 类型判断函数

复制代码
// 精准类型判断
function getType(value) {
  // 处理null的特殊情况
  if (value === null) {
    return 'null';
  }
  
  // 处理基本类型
  const type = typeof value;
  if (type !== 'object') {
    return type;
  }
  
  // 使用Object.prototype.toString获取详细类型
  const typeString = Object.prototype.toString.call(value);
  // 截取类型字符串,如"[object Array]" -> "array"
  return typeString.slice(8, -1).toLowerCase();
}

// 测试所有类型
const testCases = [
  null,           // 'null'
  undefined,      // 'undefined'
  123,            // 'number'
  'hello',        // 'string'
  true,           // 'boolean'
  Symbol('sym'),  // 'symbol'
  [],             // 'array'
  {},             // 'object'
  new Date(),     // 'date'
  /regex/,        // 'regexp'
  new Map(),      // 'map'
  new Set(),      // 'set'
  function() {},  // 'function'
  new Error()     // 'error'
];

testCases.forEach(item => {
  console.log(`${item} -> ${getType(item)}`);
});

为什么不用typeof?

typeof无法区分数组、null和普通对象,都返回'object'。Object.prototype.toString能准确判断内置对象类型。

二、原型与继承篇

1. call/apply/bind实现

复制代码
// 1. call方法实现
Function.prototype.myCall = function(context, ...args) {
  // 处理context为null或undefined的情况
  context = context || window;
  
  // 创建唯一的key,避免属性冲突
  const fnKey = Symbol('fn');
  
  // 将当前函数绑定到context上
  context[fnKey] = this;
  
  // 执行函数
  const result = context[fnKey](...args);
  
  // 删除临时属性
  delete context[fnKey];
  
  return result;
};

// 2. apply方法实现
Function.prototype.myApply = function(context, args) {
  context = context || window;
  const fnKey = Symbol('fn');
  context[fnKey] = this;
  
  // apply接收数组参数
  const result = Array.isArray(args) 
    ? context[fnKey](...args) 
    : context[fnKey]();
  
  delete context[fnKey];
  return result;
};

// 3. bind方法实现(支持new操作符)
Function.prototype.myBind = function(context, ...bindArgs) {
  const self = this;
  
  // 返回绑定函数
  const boundFunction = function(...callArgs) {
    // 判断是否被new调用
    const isNewCall = this instanceof boundFunction;
    
    // 如果是new调用,this指向新创建的对象
    // 否则使用传入的context
    const thisArg = isNewCall ? this : context;
    
    return self.apply(thisArg, bindArgs.concat(callArgs));
  };
  
  // 维护原型关系,支持instanceof
  if (this.prototype) {
    boundFunction.prototype = Object.create(this.prototype);
  }
  
  return boundFunction;
};

// 使用示例
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: 'Alice' };

// 测试call
greet.myCall(person, 'Hello', '!'); // Hello, Alice!

// 测试apply
greet.myApply(person, ['Hi', '!!']); // Hi, Alice!!

// 测试bind
const boundGreet = greet.myBind(person, 'Hey');
boundGreet('...'); // Hey, Alice...

// 测试bind后使用new
function Person(name) {
  this.name = name;
}
const BoundPerson = Person.myBind({});
const p = new BoundPerson('Bob');
console.log(p.name); // Bob
console.log(p instanceof Person); // true

关键难点:

  • call/apply的核心是将函数作为上下文对象的属性调用

  • bind需要处理new操作符的情况,保持原型链关系

  • 使用Symbol避免属性名冲突

2. 原型链继承

复制代码
// 1. 原型链继承(不推荐,有共享问题)
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  this.age = age;
}

// 继承Parent
Child.prototype = new Parent(); // 问题:所有实例共享colors

const child1 = new Child('小明', 10);
child1.colors.push('green');

const child2 = new Child('小红', 12);
console.log(child2.colors); // ['red', 'blue', 'green'] 被污染了!

// 2. 构造函数继承(推荐组合式)
function Parent2(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

function Child2(name, age) {
  // 借用父类构造函数
  Parent2.call(this, name); // 关键:调用父类构造函数
  this.age = age;
}

// 设置原型链
Child2.prototype = Object.create(Parent2.prototype);
// 修复constructor指向
Child2.prototype.constructor = Child2;

// 添加子类方法
Child2.prototype.sayAge = function() {
  console.log(this.age);
};

// 测试
const c1 = new Child2('小明', 10);
c1.colors.push('green');

const c2 = new Child2('小红', 12);
console.log(c2.colors); // ['red', 'blue'] 不受影响

继承方案比较:

  • 原型链继承:父类实例属性被所有子类实例共享

  • 构造函数继承:无法继承父类原型上的方法

  • 组合继承(推荐):结合两者优点,最常用

三、函数与作用域篇

1. 防抖与节流

复制代码
// 1. 防抖函数:最后一次触发后执行
function debounce(func, wait, immediate = false) {
  let timeout = null;
  let result;
  
  return function(...args) {
    const context = this;
    
    // 清除之前的定时器
    if (timeout) clearTimeout(timeout);
    
    if (immediate) {
      // 立即执行版本
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);
      
      if (callNow) result = func.apply(context, args);
    } else {
      // 延迟执行版本
      timeout = setTimeout(() => {
        func.apply(context, args);
      }, wait);
    }
    
    return result;
  };
}

// 2. 节流函数:固定间隔执行
function throttle(func, wait, options = {}) {
  let timeout = null;
  let previous = 0;
  const { leading = true, trailing = true } = options;
  
  return function(...args) {
    const context = this;
    const now = Date.now();
    
    // 首次不立即执行
    if (!previous && !leading) previous = now;
    
    const remaining = wait - (now - previous);
    
    if (remaining <= 0 || remaining > wait) {
      // 清除定时器
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      
      previous = now;
      func.apply(context, args);
    } else if (!timeout && trailing) {
      // 设置定时器,在剩余时间后执行
      timeout = setTimeout(() => {
        previous = !leading ? 0 : Date.now();
        timeout = null;
        func.apply(context, args);
      }, remaining);
    }
  };
}

// 使用示例
const input = document.createElement('input');
document.body.appendChild(input);

// 防抖:输入停止500ms后搜索
input.addEventListener('input', debounce(function(e) {
  console.log('搜索:', e.target.value);
}, 500));

// 节流:每200ms最多执行一次滚动处理
window.addEventListener('scroll', throttle(function() {
  console.log('滚动位置:', window.scrollY);
}, 200));

// 测试
for (let i = 0; i < 5; i++) {
  setTimeout(() => {
    input.value = `test${i}`;
    input.dispatchEvent(new Event('input'));
  }, i * 100);
}

应用场景:

  • 防抖:搜索框输入、窗口resize结束

  • 节流:滚动事件、鼠标移动、频繁点击

2. 柯里化函数

复制代码
// 柯里化函数:将多参数函数转换为一系列单参数函数
function curry(fn) {
  // 获取函数参数个数
  const arity = fn.length;
  
  return function curried(...args) {
    // 如果参数足够,直接执行
    if (args.length >= arity) {
      return fn.apply(this, args);
    } else {
      // 参数不足,返回新函数继续接收参数
      return function(...moreArgs) {
        return curried.apply(this, args.concat(moreArgs));
      };
    }
  };
}

// 支持占位符的柯里化
function curryWithPlaceholder(fn) {
  return function curried(...args) {
    // 过滤占位符
    const complete = args.length >= fn.length && 
      !args.slice(0, fn.length).includes(curryWithPlaceholder.placeholder);
    
    if (complete) {
      return fn.apply(this, args);
    } else {
      return function(...newArgs) {
        // 合并参数,处理占位符
        const mergedArgs = args.map(arg => 
          arg === curryWithPlaceholder.placeholder && newArgs.length
            ? newArgs.shift()
            : arg
        ).concat(newArgs);
        
        return curried.apply(this, mergedArgs);
      };
    }
  };
}

// 占位符
curryWithPlaceholder.placeholder = Symbol();

// 使用示例
function add(a, b, c) {
  return a + b + c;
}

// 普通柯里化
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6

// 占位符柯里化
const _ = curryWithPlaceholder.placeholder;
const curriedAdd2 = curryWithPlaceholder(add);
console.log(curriedAdd2(1, _, 3)(2)); // 6
console.log(curriedAdd2(_, 2)(1, 3)); // 6

柯里化优势:

  • 参数复用

  • 延迟计算

  • 函数组合更灵活

四、数据结构与算法篇

1. 实现LRU缓存

复制代码
// 双向链表节点
class ListNode {
  constructor(key, value) {
    this.key = key;
    this.value = value;
    this.prev = null;
    this.next = null;
  }
}

// LRU缓存实现
class LRUCache {
  constructor(capacity) {
    this.capacity = capacity; // 缓存容量
    this.cache = new Map();   // 快速查找
    this.size = 0;           // 当前大小
    
    // 初始化双向链表
    this.head = new ListNode(0, 0); // 虚拟头节点
    this.tail = new ListNode(0, 0); // 虚拟尾节点
    this.head.next = this.tail;
    this.tail.prev = this.head;
  }
  
  // 获取缓存
  get(key) {
    if (!this.cache.has(key)) {
      return -1; // 缓存未命中
    }
    
    const node = this.cache.get(key);
    // 移动到链表头部(最近使用)
    this.moveToHead(node);
    return node.value;
  }
  
  // 添加缓存
  put(key, value) {
    if (this.cache.has(key)) {
      // 已存在,更新值并移到头部
      const node = this.cache.get(key);
      node.value = value;
      this.moveToHead(node);
    } else {
      // 创建新节点
      const newNode = new ListNode(key, value);
      this.cache.set(key, newNode);
      this.addToHead(newNode);
      this.size++;
      
      // 超过容量,移除最近最少使用的节点
      if (this.size > this.capacity) {
        const removed = this.removeTail();
        this.cache.delete(removed.key);
        this.size--;
      }
    }
  }
  
  // 辅助方法:添加到头部
  addToHead(node) {
    node.prev = this.head;
    node.next = this.head.next;
    this.head.next.prev = node;
    this.head.next = node;
  }
  
  // 辅助方法:移除节点
  removeNode(node) {
    node.prev.next = node.next;
    node.next.prev = node.prev;
  }
  
  // 辅助方法:移动到头部
  moveToHead(node) {
    this.removeNode(node);
    this.addToHead(node);
  }
  
  // 辅助方法:移除尾节点
  removeTail() {
    const node = this.tail.prev;
    this.removeNode(node);
    return node;
  }
  
  // 打印缓存内容(调试用)
  print() {
    const result = [];
    let current = this.head.next;
    while (current !== this.tail) {
      result.push(`${current.key}:${current.value}`);
      current = current.next;
    }
    console.log(`Cache: [${result.join(' -> ')}]`);
  }
}

// 使用示例
const cache = new LRUCache(2);

cache.put(1, 1); // 缓存是 {1=1}
cache.put(2, 2); // 缓存是 {1=1, 2=2}
console.log(cache.get(1));    // 返回 1
cache.put(3, 3);             // 该操作会使得关键字 2 作废
console.log(cache.get(2));    // 返回 -1 (未找到)
cache.put(4, 4);             // 该操作会使得关键字 1 作废
console.log(cache.get(1));    // 返回 -1 (未找到)
console.log(cache.get(3));    // 返回 3
console.log(cache.get(4));    // 返回 4

LRU原理:

  • 最近最少使用,当缓存满时淘汰最久未访问的数据

  • 使用Map实现O(1)查找

  • 使用双向链表实现O(1)插入和删除

2. 有效括号(LeetCode 20)

复制代码
// 有效的括号
function isValid(s) {
  // 空字符串视为有效
  if (s.length === 0) return true;
  
  // 括号映射表
  const map = {
    ')': '(',
    ']': '[',
    '}': '{'
  };
  
  // 使用栈存储左括号
  const stack = [];
  
  for (let i = 0; i < s.length; i++) {
    const char = s[i];
    
    if (['(', '[', '{'].includes(char)) {
      // 左括号入栈
      stack.push(char);
    } else {
      // 右括号,检查栈顶是否匹配
      if (stack.length === 0 || stack.pop() !== map[char]) {
        return false;
      }
    }
  }
  
  // 栈为空说明所有括号都匹配
  return stack.length === 0;
}

// 测试用例
console.log(isValid("()"));        // true
console.log(isValid("()[]{}"));    // true
console.log(isValid("(]"));        // false
console.log(isValid("([)]"));      // false
console.log(isValid("{[]}"));      // true
console.log(isValid(""));          // true
console.log(isValid("((("));       // false
console.log(isValid(")))"));       // false

// 扩展:支持更多括号类型
function isValidExtended(s) {
  const map = {
    ')': '(',
    ']': '[',
    '}': '{',
    '>': '<',
    '>>': '<<'
  };
  
  const stack = [];
  
  for (const char of s) {
    if (Object.values(map).includes(char)) {
      // 左括号
      stack.push(char);
    } else if (map[char]) {
      // 右括号
      if (stack.length === 0 || stack.pop() !== map[char]) {
        return false;
      }
    }
    // 忽略其他字符
  }
  
  return stack.length === 0;
}

算法思路:

  • 遇到左括号就入栈

  • 遇到右括号就检查栈顶是否匹配

  • 最后栈应为空

  • 时间复杂度O(n),空间复杂度O(n)

五、Promise与异步篇

1. 手写Promise(简化版)

复制代码
// Promise三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

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 (error) {
      reject(error);
    }
  }
  
  then(onFulfilled, onRejected) {
    // 参数处理
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
    
    // 返回新的Promise
    const promise2 = new MyPromise((resolve, reject) => {
      const handleFulfilled = () => {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      };
      
      const handleRejected = () => {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      };
      
      if (this.state === FULFILLED) {
        handleFulfilled();
      } else if (this.state === REJECTED) {
        handleRejected();
      } else {
        this.onFulfilledCallbacks.push(handleFulfilled);
        this.onRejectedCallbacks.push(handleRejected);
      }
    });
    
    return promise2;
  }
  
  resolvePromise(promise2, x, resolve, reject) {
    // 防止循环引用
    if (promise2 === x) {
      return reject(new TypeError('Chaining cycle detected'));
    }
    
    // 如果x是Promise
    if (x instanceof MyPromise) {
      x.then(
        value => this.resolvePromise(promise2, value, resolve, reject),
        reject
      );
    } 
    // 如果x是对象或函数
    else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
      let then;
      try {
        then = x.then;
      } catch (error) {
        return reject(error);
      }
      
      if (typeof then === 'function') {
        let called = false;
        try {
          then.call(
            x,
            value => {
              if (called) return;
              called = true;
              this.resolvePromise(promise2, value, resolve, reject);
            },
            reason => {
              if (called) return;
              called = true;
              reject(reason);
            }
          );
        } catch (error) {
          if (!called) {
            reject(error);
          }
        }
      } else {
        resolve(x);
      }
    } else {
      resolve(x);
    }
  }
  
  catch(onRejected) {
    return this.then(null, onRejected);
  }
  
  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),
      reason => MyPromise.resolve(callback()).then(() => { throw reason })
    );
  }
  
  static resolve(value) {
    if (value instanceof MyPromise) {
      return value;
    }
    return new MyPromise(resolve => resolve(value));
  }
  
  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason));
  }
}

// 测试
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('成功'), 1000);
});

p.then(value => {
  console.log(value); // 1秒后输出: 成功
  return '新的值';
}).then(value => {
  console.log(value); // 输出: 新的值
});

2. Promise静态方法实现

复制代码
// Promise.all
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('参数必须是数组'));
    }
    
    const results = new Array(promises.length);
    let count = 0;
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        value => {
          results[index] = value;
          count++;
          
          if (count === promises.length) {
            resolve(results);
          }
        },
        reason => {
          // 有一个失败就立即拒绝
          reject(reason);
        }
      );
    });
  });
};

// Promise.race
Promise.myRace = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('参数必须是数组'));
    }
    
    promises.forEach(promise => {
      Promise.resolve(promise).then(resolve, reject);
    });
  });
};

// Promise.allSettled
Promise.myAllSettled = function(promises) {
  return new Promise(resolve => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('参数必须是数组'));
    }
    
    const results = new Array(promises.length);
    let count = 0;
    
    const processResult = (result, index, status) => {
      results[index] = {
        status,
        [status === 'fulfilled' ? 'value' : 'reason']: result
      };
      count++;
      
      if (count === promises.length) {
        resolve(results);
      }
    };
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        value => processResult(value, index, 'fulfilled'),
        reason => processResult(reason, index, 'rejected')
      );
    });
  });
};

// 测试
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = new Promise((resolve) => setTimeout(() => resolve(3), 100));
const p4 = Promise.reject('错误');

Promise.myAll([p1, p2, p3]).then(console.log); // [1, 2, 3]
Promise.myRace([p3, p1]).then(console.log);    // 1
Promise.myAllSettled([p1, p4]).then(console.log);
// [{status: "fulfilled", value: 1}, {status: "rejected", reason: "错误"}]

Promise关键点:

  • 三种状态:pending、fulfilled、rejected

  • 状态不可逆

  • 支持链式调用

  • 微任务执行时机

六、框架原理篇

1. Vue响应式原理(精简版)

复制代码
// 依赖收集类
class Dep {
  constructor() {
    this.subscribers = new Set();
  }
  
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
  
  notify() {
    this.subscribers.forEach(effect => effect());
  }
}

let activeEffect = null;

// 观察者函数
function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}

// 响应式系统
function reactive(obj) {
  // 存储依赖关系
  const deps = new Map();
  
  return new Proxy(obj, {
    get(target, key) {
      // 收集依赖
      let dep = deps.get(key);
      if (!dep) {
        dep = new Dep();
        deps.set(key, dep);
      }
      dep.depend();
      
      return target[key];
    },
    
    set(target, key, value) {
      target[key] = value;
      
      // 触发更新
      const dep = deps.get(key);
      if (dep) {
        dep.notify();
      }
      
      return true;
    }
  });
}

// 使用示例
const state = reactive({
  count: 0,
  message: 'Hello'
});

// 自动追踪依赖
watchEffect(() => {
  console.log(`Count: ${state.count}, Message: ${state.message}`);
});

// 触发更新
state.count = 1; // 输出: Count: 1, Message: Hello
state.message = 'World'; // 输出: Count: 1, Message: World

2. Vue3 computed实现

复制代码
function computed(getter) {
  let value;
  let dirty = true;
  
  const effect = () => {
    value = getter();
    dirty = false;
  };
  
  // 创建响应式依赖
  const deps = new Dep();
  
  // 当依赖变化时标记为脏
  watchEffect(() => {
    getter();
    dirty = true;
    deps.notify();
  });
  
  return {
    get value() {
      if (dirty) {
        effect();
      }
      deps.depend();
      return value;
    }
  };
}

// 使用
const state = reactive({ price: 100, quantity: 2 });
const total = computed(() => state.price * state.quantity);

watchEffect(() => {
  console.log(`Total: ${total.value}`);
});

state.price = 200; // 输出: Total: 400

七、高频附加题

1. 深拷贝完整实现

复制代码
function deepClone(obj, hash = new WeakMap()) {
  // 基本类型直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  // 处理特殊对象类型
  if (obj instanceof Date) {
    return new Date(obj);
  }
  
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
  
  if (obj instanceof Map) {
    const clone = new Map();
    hash.set(obj, clone);
    for (const [key, val] of obj) {
      clone.set(deepClone(key, hash), deepClone(val, hash));
    }
    return clone;
  }
  
  if (obj instanceof Set) {
    const clone = new Set();
    hash.set(obj, clone);
    for (const val of obj) {
      clone.add(deepClone(val, hash));
    }
    return clone;
  }
  
  // 处理数组和对象
  const cloneObj = Array.isArray(obj) ? [] : {};
  hash.set(obj, cloneObj);
  
  // 获取所有属性(包括Symbol)
  const allKeys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];
  
  for (const key of allKeys) {
    cloneObj[key] = deepClone(obj[key], hash);
  }
  
  return cloneObj;
}

// 测试
const obj = {
  a: 1,
  b: 'string',
  c: true,
  d: null,
  e: undefined,
  f: new Date(),
  g: /regex/gi,
  h: [1, 2, { nested: 'object' }],
  i: { nested: { deep: 'object' } },
  j: new Map([['key', 'value']]),
  k: new Set([1, 2, 3]),
  l: Symbol('sym'),
  // 循环引用
  m: {}
};
obj.m.self = obj;
obj.circular = obj;

const cloned = deepClone(obj);
console.log(cloned);
console.log(cloned !== obj); // true
console.log(cloned.m.self === cloned); // true

2. 数组扁平化

复制代码
// 方法1: 递归
function flattenRecursive(arr) {
  return arr.reduce((acc, val) => {
    return acc.concat(Array.isArray(val) ? flattenRecursive(val) : val);
  }, []);
}

// 方法2: 迭代
function flattenIterative(arr) {
  const stack = [...arr];
  const result = [];
  
  while (stack.length) {
    const next = stack.pop();
    if (Array.isArray(next)) {
      stack.push(...next);
    } else {
      result.unshift(next);
    }
  }
  
  return result;
}

// 方法3: 指定深度
function flattenDepth(arr, depth = 1) {
  if (depth === 0) return arr.slice();
  
  return arr.reduce((acc, val) => {
    if (Array.isArray(val) && depth > 0) {
      return acc.concat(flattenDepth(val, depth - 1));
    } else {
      return acc.concat(val);
    }
  }, []);
}

// 方法4: Generator
function* flattenGenerator(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) {
      yield* flattenGenerator(item);
    } else {
      yield item;
    }
  }
}

// 测试
const nestedArray = [1, [2, [3, [4, 5]], 6], 7];

console.log(flattenRecursive(nestedArray)); // [1, 2, 3, 4, 5, 6, 7]
console.log(flattenIterative(nestedArray)); // [1, 2, 3, 4, 5, 6, 7]
console.log(flattenDepth(nestedArray, 2));  // [1, 2, 3, [4, 5], 6, 7]

const gen = flattenGenerator(nestedArray);
console.log([...gen]); // [1, 2, 3, 4, 5, 6, 7]

八、大厂真题精选

1. 字节跳动:Promise.allSettled

复制代码
// 已在上文实现,这里补充完整测试
Promise.myAllSettled = function(promises) {
  return new Promise(resolve => {
    if (!Array.isArray(promises)) {
      throw new TypeError('参数必须是数组');
    }
    
    const results = new Array(promises.length);
    let completed = 0;
    
    const checkComplete = () => {
      if (++completed === promises.length) {
        resolve(results);
      }
    };
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(
          value => {
            results[index] = { status: 'fulfilled', value };
            checkComplete();
          },
          reason => {
            results[index] = { status: 'rejected', reason };
            checkComplete();
          }
        );
    });
    
    // 处理空数组
    if (promises.length === 0) {
      resolve([]);
    }
  });
};

// 测试
const promises = [
  Promise.resolve(1),
  Promise.reject('error1'),
  new Promise(resolve => setTimeout(() => resolve(2), 100)),
  Promise.reject('error2')
];

Promise.myAllSettled(promises).then(results => {
  console.log(results);
  /*
  [
    { status: 'fulfilled', value: 1 },
    { status: 'rejected', reason: 'error1' },
    { status: 'fulfilled', value: 2 },
    { status: 'rejected', reason: 'error2' }
  ]
  */
});

2. 得物:虚拟列表

复制代码
class VirtualList {
  constructor(container, options) {
    this.container = container;
    this.data = options.data || [];
    this.itemHeight = options.itemHeight || 50;
    this.renderItem = options.renderItem;
    this.bufferSize = options.bufferSize || 5;
    
    this.visibleCount = 0;
    this.startIndex = 0;
    this.endIndex = 0;
    
    this.init();
  }
  
  init() {
    // 创建容器
    this.wrapper = document.createElement('div');
    this.wrapper.style.position = 'relative';
    this.wrapper.style.height = `${this.data.length * this.itemHeight}px`;
    
    this.container.style.overflow = 'auto';
    this.container.appendChild(this.wrapper);
    
    // 计算可见数量
    this.visibleCount = Math.ceil(this.container.clientHeight / this.itemHeight) + this.bufferSize * 2;
    
    // 监听滚动
    this.container.addEventListener('scroll', this.handleScroll.bind(this));
    
    // 初始渲染
    this.renderVisibleItems();
  }
  
  handleScroll() {
    const scrollTop = this.container.scrollTop;
    this.startIndex = Math.floor(scrollTop / this.itemHeight) - this.bufferSize;
    this.startIndex = Math.max(0, this.startIndex);
    
    this.endIndex = this.startIndex + this.visibleCount;
    this.endIndex = Math.min(this.endIndex, this.data.length);
    
    this.renderVisibleItems();
  }
  
  renderVisibleItems() {
    // 清空现有内容
    this.wrapper.innerHTML = '';
    
    // 创建可见项
    for (let i = this.startIndex; i < this.endIndex; i++) {
      const item = this.data[i];
      const element = this.renderItem(item, i);
      
      element.style.position = 'absolute';
      element.style.top = `${i * this.itemHeight}px`;
      element.style.width = '100%';
      element.style.height = `${this.itemHeight}px`;
      
      this.wrapper.appendChild(element);
    }
  }
  
  updateData(newData) {
    this.data = newData;
    this.wrapper.style.height = `${this.data.length * this.itemHeight}px`;
    this.renderVisibleItems();
  }
}

// 使用示例
const container = document.createElement('div');
container.style.height = '400px';
container.style.border = '1px solid #ccc';
document.body.appendChild(container);

// 生成测试数据
const data = Array.from({ length: 10000 }, (_, i) => ({
  id: i + 1,
  text: `Item ${i + 1}`,
  color: `hsl(${i % 360}, 70%, 70%)`
}));

// 创建虚拟列表
const list = new VirtualList(container, {
  data,
  itemHeight: 50,
  bufferSize: 5,
  renderItem: (item) => {
    const div = document.createElement('div');
    div.textContent = item.text;
    div.style.backgroundColor = item.color;
    div.style.display = 'flex';
    div.style.alignItems = 'center';
    div.style.padding = '0 20px';
    return div;
  }
});

附:手写题练习建议

1. 分阶段学习路径

复制代码
初级阶段(1-2周):
  - new/instanceof
  - 防抖/节流
  - 数组扁平化/去重
  - 深拷贝浅拷贝

中级阶段(2-3周):
  - call/apply/bind
  - Promise基本实现
  - 柯里化/组合函数
  - 数组常用方法

高级阶段(3-4周):
  - Promise.all/race/allSettled
  - 完整Promise实现
  - 响应式原理
  - LRU/发布订阅

2. 练习方法

  1. 理解优先:先理解原理,再动手实现

  2. 测试驱动:为每个函数编写测试用例

  3. 对比源码:对比自己的实现和标准实现的差异

  4. 性能优化:考虑时间复杂度和空间复杂度

  5. 边界处理:考虑各种边界条件和异常情况

3. 常见陷阱

复制代码
// 1. 循环引用
const obj = { a: 1 };
obj.self = obj; // 深拷贝时要注意

// 2. 特殊对象
new Date();      // 日期对象
/abc/;          // 正则对象
new Map();      // Map对象
new Set();      // Set对象

// 3. 性能问题
// 大数据量时注意算法复杂度
// 递归注意栈溢出

// 4. 副作用
// 纯函数避免修改输入参数
// 注意异步操作的顺序

总结

手写代码考察的是对JavaScript语言本质的理解。通过反复练习这些题目,不仅能帮助你在面试中脱颖而出,更重要的是能深入理解JavaScript的运行机制,在实际开发中写出更健壮、高效的代码。

**最后建议:**​ 不要死记硬背,理解每个实现背后的设计思想和应用场景。在理解的基础上,尝试自己从头实现,并考虑各种边界情况,这样才能真正掌握。


**祝大家面试顺利,拿到心仪的offer!**​

本文代码已通过测试,可直接复制使用。如有疑问或发现错误,欢迎在评论区交流讨论。

相关推荐
寻寻觅觅☆7 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
萧曵 丶8 小时前
Vue 中父子组件之间最常用的业务交互场景
javascript·vue.js·交互
l1t8 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
赶路人儿8 小时前
Jsoniter(java版本)使用介绍
java·开发语言
Amumu121389 小时前
Vue3扩展(二)
前端·javascript·vue.js
NEXT069 小时前
JavaScript进阶:深度剖析函数柯里化及其在面试中的底层逻辑
前端·javascript·面试
ceclar1239 小时前
C++使用format
开发语言·c++·算法
码说AI9 小时前
python快速绘制走势图对比曲线
开发语言·python
Gofarlic_OMS9 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
星空下的月光影子10 小时前
易语言开发从入门到精通:补充篇·网络爬虫与自动化采集分析系统深度实战·HTTP/HTTPS请求·HTML/JSON解析·反爬策略·电商价格监控·新闻资讯采集
开发语言