前端手写代码大全

前端手写代码大全

一、JavaScript 基础手写

1.1 call / apply / bind 实现

1.1.1 手写 call
javascript 复制代码
Function.prototype.myCall = function (context, ...args) {
  // 1. 判断 context 是否为 null/undefined,如果是则设置为 window(浏览器环境)
  //    这是为了处理 myCall() 这样不传第一个参数的情况
  context = context || window;

  // 2. 在 context 上创建一个临时方法,命名为 __fn__
  //    this 指向调用 myCall 的函数(即被借用的函数)
  //    例如:obj.myCall(func) 中,this 就是 func
  context.__fn__ = this;

  // 3. 执行这个临时方法,传入展开后的参数
  //    例如:myCall(obj, 'a', 'b') 会变成 context.__fn__('a', 'b')
  const result = context.__fn__(...args);

  // 4. 删除临时方法,避免污染传入的对象
  //    这是必要的清理工作,保持对象的纯净
  delete context.__fn__;

  // 5. 返回执行结果
  //    如果原函数有返回值,就返回;如果是 void 函数,则返回 undefined
  return result;
};

使用示例:

javascript 复制代码
function greet(name, age) {
  console.log(`Hello, I'm ${name}, ${age} years old`);
  return `${this.job} - ${name}`;
}

const person = { job: 'engineer' };
console.log(greet.myCall(person, 'Alice', 25));
// 输出: Hello, I'm Alice, 25 years old
// 返回: engineer - Alice

1.1.2 手写 apply
javascript 复制代码
Function.prototype.myApply = function (context, args = []) {
  // 1. 同样处理 context 为 null/undefined 的情况
  //    确保 context 有默认值,避免后续操作报错
  context = context || window;

  // 2. 在 context 上创建临时方法
  //    与 call 的区别:apply 接收的是数组参数
  context.__fn__ = this;

  // 3. 执行临时方法,传入参数数组
  //    注意:这里直接传 args 数组,而不是展开
  //    因为 apply 的特点就是接收数组参数
  const result = context.__fn__(...args);

  // 4. 清理临时方法
  delete context.__fn__;

  // 5. 返回结果
  return result;
};

使用示例:

javascript 复制代码
const numbers = [1, 2, 3, 4, 5];
const max = Math.max.apply(null, numbers);
// 等价于: Math.max(...numbers)
// myApply 版本: Math.max.myApply(null, [numbers])
console.log(max); // 5

1.1.3 手写 bind
javascript 复制代码
Function.prototype.myBind = function (context, ...bindArgs) {
  // 1. 保存当前函数的引用
  //    this 指向调用 myBind 的那个函数
  //    例如:func.myBind(obj) 中,this 就是 func
  const originalFunc = this;

  // 2. 返回一个新函数
  //    这个新函数会在调用时执行 originalFunc,并绑定 this 到 context
  return function (...callArgs) {
    // 3. 合并参数:bind 时传入的参数 + 调用时传入的参数
    //    例如:func.myBind(obj, 'a')('b') 那么 args = ['a', 'b']
    //    这样实现的是"柯里化"效果
    const allArgs = [...bindArgs, ...callArgs];

    // 4. 使用 call 执行函数,手动绑定 this
    //    注意:这里必须用 call 而不是直接调用
    //    因为返回的函数在执行时,this 默认指向 window(严格模式为 undefined)
    return originalFunc.call(context, ...allArgs);
  };
};

使用示例:

javascript 复制代码
function greet(name) {
  return `Hello ${name}, I am a ${this.role}`;
}

const person = { role: 'developer' };
const boundGreet = greet.myBind(person);

console.log(boundGreet('Alice')); // Hello Alice, I am a developer
console.log(boundGreet('Bob'));   // Hello Bob, I am a developer

// 带预设参数的场景
const sayHello = greet.myBind(person, 'Charlie');
console.log(sayHello()); // Hello Charlie, I am a developer

1.2 new 操作符实现

javascript 复制代码
function myNew(constructor, ...args) {
  // 1. 创建一个空对象,继承 constructor 的原型
  //    Object.create() 创建一个新对象,其 [[Prototype]] 指向构造函数的 prototype
  //    这是实现原型链继承的关键步骤
  const instance = Object.create(constructor.prototype);

  // 2. 调用构造函数,传入参数,绑定 this 到新创建的对象
  //    constructor.call(instance, ...args) 会执行构造函数
  //    此时 instance 就会有构造函数中定义的属性
  const result = constructor.apply(instance, args);

  // 3. 如果构造函数返回的是对象,则返回该对象;否则返回我们的 instance
  //    这是处理构造函数有显式返回值的情况
  //    如果构造函数返回原始值(string/number/boolean/null/undefined),则忽略
  return result instanceof Object ? result : instance;
}

使用示例:

javascript 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
  // 如果构造函数返回对象,则 new 会返回那个对象
  // return { custom: true }; // 注释掉这行,返回 Person 实例
}

Person.prototype.sayHello = function () {
  return `Hi, I'm ${this.name}`;
};

const alice = myNew(Person, 'Alice', 25);
console.log(alice.name);     // Alice
console.log(alice.age);      // 25
console.log(alice.sayHello()); // Hi, I'm Alice
console.log(alice instanceof Person); // true

1.3 浅拷贝 / 深拷贝实现

1.3.1 浅拷贝
javascript 复制代码
function shallowClone(target) {
  // 1. 判断 target 是否为数组或对象
  //    如果不是,直接返回(对于原始值如 number/string,这没问题)
  if (typeof target !== 'object' || target === null) {
    return target;
  }

  // 2. 根据 target 类型决定创建数组还是对象
  //    Array.isArray() 用来区分数组和普通对象
  const cloneTarget = Array.isArray(target) ? [] : {};

  // 3. 遍历 target 的所有可枚举属性
  //    for...in 会遍历自身和继承的可枚举属性
  for (const key in target) {
    // 4. 确保只拷贝自身的属性,不拷贝继承的属性
    //    hasOwnProperty() 检查属性是否为对象自身的(而非原型链上的)
    if (target.hasOwnProperty(key)) {
      // 5. 直接赋值,实现浅拷贝
      //    问题:如果是引用值(对象/数组),只复制引用,不复制值
      cloneTarget[key] = target[key];
    }
  }

  return cloneTarget;
}

浅拷贝的问题演示:

javascript 复制代码
const original = {
  name: 'Alice',
  info: { hobby: 'reading' }  // 嵌套对象是引用类型
};

const cloned = shallowClone(original);
cloned.name = 'Bob';
cloned.info.hobby = 'gaming';

console.log(original.name);   // Alice(不受影响)
console.log(original.info.hobby); // gaming(被影响了!)
// 原因:浅拷贝只复制了 info 对象的引用,而不是值

1.3.2 深拷贝(递归版本)
javascript 复制代码
function deepClone(target, weakMap = new WeakMap()) {
  // 1. 处理原始类型和函数
  //    typeof function === 'function',直接返回(函数不可复制)
  if (typeof target === 'symbol') {
    // Symbol 作为 key 时需要特殊处理,创建新的 Symbol
    return Symbol(target.description);
  }

  if (typeof target !== 'object' || target === null) {
    return target;
  }

  // 2. 处理循环引用
  //    使用 WeakMap 存储已拷贝过的对象,防止无限递归
  //    例如:obj.self = obj 这种自引用情况
  if (weakMap.has(target)) {
    return weakMap.get(target);
  }

  // 3. 区分数组和对象
  const cloneTarget = Array.isArray(target) ? [] : {};

  // 4. 记录当前拷贝的对象,用于处理循环引用
  weakMap.set(target, cloneTarget);

  // 5. 遍历所有属性,递归拷贝
  for (const key in target) {
    if (target.hasOwnProperty(key)) {
      // 递归调用:对每个属性值进行深拷贝
      // 如果是原始值,直接返回;如果是对象,递归处理
      cloneTarget[key] = deepClone(target[key], weakMap);
    }
  }

  return cloneTarget;
}

使用示例:

javascript 复制代码
const original = {
  name: 'Alice',
  info: { hobby: 'reading', scores: [90, 85, 92] },
  date: new Date(),
  reg: /abc/gi,
  nested: { self: null }
};
original.nested.self = original; // 循环引用

const cloned = deepClone(original);
cloned.info.hobby = 'gaming';
cloned.info.scores.push(100);

console.log(original.info.hobby);  // reading(不受影响)
console.log(original.info.scores); // [90, 85, 92](不受影响)
console.log(cloned.nested.self === cloned); // true(循环引用被正确处理)

1.3.3 深拷贝(JSON 方法,简单但不完美)
javascript 复制代码
function deepCloneJSON(target) {
  // 1. JSON.stringify() 将对象转为 JSON 字符串
  //    过程中会处理循环引用报错(所以这种方式不能处理循环引用)
  // 2. JSON.parse() 将 JSON 字符串转回对象
  //    缺点:
  //    - 无法处理函数、undefined、Symbol
  //    - 无法处理 Date 对象(会变成字符串)
  //    - 无法处理正则(会变成空对象)
  //    - 无法处理循环引用(会报错)
  return JSON.parse(JSON.stringify(target));
}

何时使用 JSON 方法:

  • 对象结构简单、不包含函数/undefined/Symbol
  • 不需要处理循环引用
  • 性能要求不高,但代码要简洁

1.4 防抖 / 节流实现

1.4.1 防抖(Debounce)
javascript 复制代码
function debounce(fn, delay, immediate = false) {
  // 1. timer 用于存储 setTimeout 的返回值,以便清除
  // 2. lastTime 用于记录上次执行的时间(用于 immediate 模式)
  let timer = null;
  let lastTime = null;

  // 3. 返回一个包装函数,替代原函数
  return function (...args) {
    // 4. 保存 this 指向
    //    原始函数的 this 应该指向调用它的对象
    //    在这里,this 应该指向 debounce 返回的函数被调用时的上下文
    const context = this;

    // 5. 每次调用都清除之前的定时器
    //    这是实现"最后一次调用有效"的关键
    //    如果用户在 delay 期间内多次调用,只有最后一次会执行
    if (timer) clearTimeout(timer);

    if (immediate && !lastTime) {
      // 6. immediate 模式:立即执行
      //    lastTime 为 null 表示还没执行过,立即执行
      fn.apply(context, args);
      lastTime = Date.now();
    } else {
      // 7. 普通模式:延迟执行
      //    每次调用都重置定时器,实现"等待"
      timer = setTimeout(() => {
        // 8. 定时器到期,执行函数
        //    注意:setTimeout 的回调函数中 this 指向 global/window
        //    所以需要用 apply 绑定正确的 this
        fn.apply(context, args);

        // 9. 重置 lastTime(用于 immediate 模式)
        //    下次 immediate 调用时,如果还在 immediate 窗口期内,就不执行
        lastTime = null;
      }, delay);
    }
  };
}

使用场景:

  • 搜索框输入(用户停止输入后才搜索)
  • 窗口 resize(调整完成后执行)
  • 表单验证(用户停止输入后验证)
javascript 复制代码
// 示例
const handleSearch = debounce((keyword) => {
  console.log('搜索:', keyword);
}, 300);

handleSearch('a');
handleSearch('ab');
handleSearch('abc'); // 只执行这一次,因为这是最后一次调用后 delay 时间到了

1.4.2 节流(Throttle)
javascript 复制代码
function throttle(fn, delay) {
  // 1. lastTime 记录上次执行的时间
  // 2. timer 用于处理 trailing 模式的延迟执行
  let lastTime = 0;
  let timer = null;

  return function (...args) {
    const context = this;

    // 3. 获取当前时间戳
    const now = Date.now();

    // 4. 计算距离上次执行的时间差
    //    如果 lastTime 为 0(首次调用),则计算时间差会很大
    //    此时 lastTime 还没设置,应该立即执行
    const remaining = delay - (now - lastTime);

    if (remaining <= 0 || remaining > delay) {
      // 5. 时间到了,执行函数
      //    remaining <= 0:超过了设定的 delay 时间
      //    remaining > delay:系统时间被调整过,或者首次调用
      if (timer) {
        // 如果有待执行的定时器,清除它
        clearTimeout(timer);
        timer = null;
      }

      // 6. 记录执行时间
      lastTime = now;

      // 7. 执行函数
      fn.apply(context, args);
    } else if (!timer) {
      // 8. 还在冷却期内,设定一个定时器
      //    定时器在 remaining 时间后执行(trailing 端执行)
      //    这样确保函数在 delay 周期结束后必然执行一次
      timer = setTimeout(() => {
        lastTime = Date.now();
        timer = null;
        fn.apply(context, args);
      }, remaining);
    }
  };
}

使用场景:

  • 按钮点击防重复(规定时间内只能点击一次)
  • 滚动加载(滚动时限制触发频率)
  • 游戏中的射击(限制射击频率)
javascript 复制代码
// 示例:滚动时每 100ms 执行一次
window.addEventListener('scroll', throttle(() => {
  console.log('滚动位置:', window.scrollY);
}, 100));

1.5 instanceof 实现

javascript 复制代码
function myInstanceof(left, right) {
  // 1. 获取右边构造函数的 prototype(原型对象)
  //    这是 instanceof 检查的关键:对象.__proto__ === constructor.prototype
  const prototype = right.prototype;

  // 2. 获取左边对象的原型
  //    left.__proto__ 指向对象的隐式原型
  //    每次循环都往原型链上游移动一位
  let proto = left.__proto__;

  // 3. 循环遍历原型链
  while (proto !== null) {
    // 4. 如果原型匹配,返回 true
    //    这是 instanceof 的核心逻辑:
    //    顺着原型链向上找,如果能找到 constructor.prototype,返回 true
    if (proto === prototype) {
      return true;
    }

    // 5. 如果没找到,继续向上查找
    //    每次循环都将 proto 指向当前原型的 __proto__
    //    这就是原型链的遍历
    proto = proto.__proto__;
  }

  // 6. 遍历完整个原型链都没找到,返回 false
  //    说明 left 不是 right 的实例
  return false;
}

使用示例:

javascript 复制代码
function Person(name) {
  this.name = name;
}
const alice = new Person('Alice');

console.log(myInstanceof(alice, Person));      // true
console.log(myInstanceof(alice, Object));      // true(原型链向上找到 Object.prototype)
console.log(myInstanceof(alice, Array));        // false
console.log(myInstanceof('str', String));      // false(原始类型不是对象)
console.log(myInstanceof(null, Object));      // false(null 没有原型)

1.6 柯里化实现

javascript 复制代码
function curry(fn) {
  // 1. 保存原函数的参数个数
  //    fn.length 返回函数声明时的参数个数(不是实际调用时的)
  const fnParamsCount = fn.length;

  // 2. 返回包装函数
  return function curried(...args) {
    // 3. 判断当前收集的参数是否足够
    //    args 是每次调用时传入的参数
    if (args.length >= fnParamsCount) {
      // 4. 参数够了,执行原函数
      //    ...args 展开参数传给原函数
      return fn.apply(this, args);
    } else {
      // 5. 参数不够,返回一个新函数继续收集参数
      //    这个新函数也叫 curried,形成闭包
      return function (...nextArgs) {
        // 6. 递归调用 curried,累加收集参数
        //    每次调用都会追加新的参数,直到够数为止
        return curried.apply(this, [...args, ...nextArgs]);
      };
    }
  };
}

使用示例:

javascript 复制代码
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
console.log(curriedAdd(1, 2, 3));    // 6

// 应用场景:参数复用
const add10 = curriedAdd(10);
console.log(add10(5));  // 15
console.log(add10(3));  // 13

1.7 数组扁平化实现

javascript 复制代码
// 方法一:reduce + 递归
function flatten(arr) {
  // 1. 使用 reduce 遍历数组
  //    acc 是累计结果,初始值为 []
  return arr.reduce((acc, cur) => {
    // 2. 判断当前元素是否为数组
    //    Array.isArray() 比 instanceof 更可靠
    if (Array.isArray(cur)) {
      // 3. 如果是数组,递归扁平化,然后拼接
      //    concat 会将扁平化后的结果合并到 acc 中
      return acc.concat(flatten(cur));
    } else {
      // 4. 如果不是数组,直接 push 到结果中
      return acc.concat(cur);
    }
  }, []); // 初始值设为空数组
}
javascript 复制代码
// 方法二:toString + split(适用于纯数字数组)
function flattenToString(arr) {
  // 1. 转成字符串,所有嵌套数组都会被逗号连接
  //    [1, [2, [3, 4]]] => "1,2,3,4"
  // 2. split 转成数组(字符串数组)
  // 3. map 转回数字(会丢失原始类型信息)
  return arr.toString().split(',').map(item => Number(item));
}
javascript 复制代码
// 方法三:JSON.parse + 正则(优雅但有局限)
function flattenJSON(arr) {
  // 原理:先把数组转成 JSON 字符串
  //       然后用正则去除所有方括号
  //       再转成数组
  // 局限:如果数组元素包含字符串形式的数字,可能出错
  return JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
}

使用示例:

javascript 复制代码
const nested = [1, [2, [3, [4, 5]]], [6, 7]];

console.log(flatten(nested));              // [1, 2, 3, 4, 5, 6, 7]
console.log(flattenToString(nested));     // [1, 2, 3, 4, 5, 6, 7]
console.log(flattenJSON(nested));         // [1, 2, 3, 4, 5, 6, 7]

1.8 数组去重实现

javascript 复制代码
// 方法一:Set(ES6+ 最简洁)
function unique(arr) {
  // 1. Set 自动去重:相同的值只会保留一个
  // 2. [...Set] 或 Array.from(Set) 转回数组
  return [...new Set(arr)];
}
javascript 复制代码
// 方法二:indexOf + filter
function uniqueIndexOf(arr) {
  // 1. indexOf 返回元素第一次出现的位置
  // 2. filter 保留 indexOf 结果等于当前索引的元素
  // 3. 这样重复的元素(indexOf 结果不等于当前 index)会被过滤掉
  return arr.filter((item, index) => arr.indexOf(item) === index);
}
javascript 复制代码
// 方法三:对象键值对(处理类型问题)
function uniqueObject(arr) {
  // 1. 用对象来记录出现过的值
  // 2. 对象的键是字符串,所以需要处理类型问题
  const seen = {};
  return arr.filter(item => {
    // 3. 使用 typeof + 键组合来区分不同类型
    //    例如:object、1number、1string 会是三个不同的键
    const key = typeof item + JSON.stringify(item);
    // 4. 检查是否已经见过
    return seen.hasOwnProperty(key) ? false : (seen[key] = true);
  });
}
javascript 复制代码
// 方法四:Map(推荐,处理类型更好)
function uniqueMap(arr) {
  // 1. Map 的键可以用任意类型,不像对象只能字符串
  // 2. has() 检查键是否存在,get() 获取值,set() 设置值
  const map = new Map();
  return arr.filter(item => !map.has(item) && map.set(item, true));
}

使用示例:

javascript 复制代码
const arr = [1, 2, 2, '2', null, null, NaN, NaN, {}, {}];

console.log(unique(arr));           // [1, 2, "2", null, NaN, {}, {}](Set 能去重数字和字符串)
console.log(uniqueIndexOf(arr));    // [1, 2, "2", null, {}, {}](NaN 不被去重,indexOf 找不到 NaN)
console.log(uniqueMap(arr));        // [1, 2, "2", null, NaN, {}, {}]

二、Promise 手写

2.1 Promise 基础实现

javascript 复制代码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  // 1. 构造函数:初始化状态和值
  constructor(executor) {
    // 2. 初始状态为 pending(等待态)
    //    状态一旦改变(fulfilled/rejected),就不再可变
    this.state = PENDING;
    this.value = undefined;  // 成功后的值
    this.reason = undefined; // 失败后的原因

    // 3. 成功回调队列(then 可以调用多次)
    //    用于处理 Promise 异步完成后注册的成功回调
    this.onFulfilledCallbacks = [];

    // 4. 失败回调队列
    //    用于处理 Promise 异步完成后注册的失败回调
    this.onRejectedCallbacks = [];

    // 5. 执行器函数立即执行
    //    需要用 try-catch 包裹,因为执行器可能抛出异常
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      // 6. 如果执行器报错,Promise 直接 rejected
      this.reject(error);
    }
  }

  // 7. resolve 函数:标记 Promise 为成功态
  resolve(value) {
    // 8. 状态只能从 pending 转为其他状态
    //    一旦改变就不能再变,防止重复 resolve/reject
    if (this.state === PENDING) {
      this.state = FULFILLED;
      this.value = value;

      // 9. 执行所有注册的成功回调
      //    使用 setTimeout 确保异步执行(符合 Promise A+ 规范)
      this.onFulfilledCallbacks.forEach(callback => {
        callback(this.value);
      });
    }
  }

  // 10. reject 函数:标记 Promise 为失败态
  reject(reason) {
    if (this.state === PENDING) {
      this.state = REJECTED;
      this.reason = reason;

      // 11. 执行所有注册的失败回调
      this.onRejectedCallbacks.forEach(callback => {
        callback(this.reason);
      });
    }
  }

  // 12. then 方法:注册成功/失败回调
  then(onFulfilled, onRejected) {
    // 13. 参数校验:如果回调不是函数,给默认值
    //    这样可以实现值的穿透(Promise 链式调用)
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

    // 14. 创建并返回新的 Promise(实现链式调用的关键)
    const promise2 = new MyPromise((resolve, reject) => {
      // 15. 根据当前状态同步执行回调(状态已经是 fulfilled/rejected)
      if (this.state === FULFILLED) {
        // 16. 使用 setTimeout 异步执行,确保 promise2 已经创建
        setTimeout(() => {
          try {
            // 17. 执行回调,可能返回普通值或 Promise
            const result = onFulfilled(this.value);
            // 18. 处理回调返回值:可能是 Promise 或普通值
            //    使用 resolvePromise 处理各种情况
            this.resolvePromise(promise2, result, resolve, reject);
          } catch (error) {
            // 19. 回调执行出错,新 Promise 状态变为 rejected
            reject(error);
          }
        }, 0);
      } else if (this.state === REJECTED) {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason);
            this.resolvePromise(promise2, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else {
        // 20. 状态还是 PENDING(异步情况),先注册回调
        this.onFulfilledCallbacks.push((value) => {
          setTimeout(() => {
            try {
              const result = onFulfilled(value);
              this.resolvePromise(promise2, result, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push((reason) => {
          setTimeout(() => {
            try {
              const result = onRejected(reason);
              this.resolvePromise(promise2, result, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }

  // 21. resolvePromise:处理回调返回值
  resolvePromise(promise2, result, resolve, reject) {
    // 22. 防止循环引用(如果返回的是同一个 promise)
    if (promise2 === result) {
      return reject(new TypeError('Chaining cycle detected'));
    }

    // 23. 判断 result 是否为 Promise(鸭子类型检测)
    //    如果有 then 方法且类型为函数,就认为是 Promise
    if (result instanceof MyPromise) {
      // 24. 如果是 Promise,执行它的 then
      result.then(resolve, reject);
    } else {
      // 25. 如果是普通值,直接 resolve
      resolve(result);
    }
  }
}

使用示例:

javascript 复制代码
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('成功!'), 1000);
});

promise
  .then(value => {
    console.log(value); // 成功!
    return '链式值';
  })
  .then(value => {
    console.log(value); // 链式值
  });

// 异步 rejected 处理
const rejectedPromise = new MyPromise((resolve, reject) => {
  reject('错误');
});

rejectedPromise
  .then(null, reason => {
    console.error(reason); // 错误
    return '恢复';
  })
  .then(value => console.log(value)); // 恢复

2.2 Promise.all 实现

javascript 复制代码
Promise.myAll = function (promises) {
  // 1. promisses 是可迭代对象(数组),转换为数组
  promises = Array.isArray(promises) ? promises : [];

  // 2. 返回新的 Promise
  return new Promise((resolve, reject) => {
    // 3. 处理空数组情况(直接 resolve 空数组)
    if (promises.length === 0) {
      resolve([]);
      return;
    }

    // 4. results 用于收集所有 Promise 的结果
    const results = [];
    // 5. counter 用于记录已完成的 Promise 数量
    let counter = 0;

    // 6. 遍历所有 Promise
    for (let i = 0; i < promises.length; i++) {
      // 7. 处理可能是普通值的情况(包装成 Promise)
      Promise.resolve(promises[i]).then(
        (value) => {
          // 8. 收集结果,保持数组顺序
          //    关键:使用索引 i,不要用 push(会导致顺序错乱)
          results[i] = value;
          // 9. 每完成一个,计数器 +1
          counter++;

          // 10. 所有 Promise 都完成后,resolve 结果数组
          if (counter === promises.length) {
            resolve(results);
          }
        },
        (reason) => {
          // 11. 任何一个 Promise 失败,整体 reject
          reject(reason);
        }
      );
    }
  });
};

使用示例:

javascript 复制代码
const promises = [
  new Promise(r => setTimeout(() => r(1), 100)),
  new Promise(r => setTimeout(() => r(2), 200)),
  new Promise(r => setTimeout(() => r(3), 50)),
];

Promise.myAll(promises).then(results => {
  console.log(results); // [1, 2, 3](按顺序,不是完成的顺序)
});

// 失败情况
const failedPromises = [
  Promise.resolve(1),
  Promise.reject('错误'),
  Promise.resolve(3),
];

Promise.myAll(failedPromises).catch(err => {
  console.error(err); // 错误
});

2.3 Promise.race 实现

javascript 复制代码
Promise.myRace = function (promises) {
  // 1. 确保 promises 是数组
  promises = Array.isArray(promises) ? promises : [];

  // 2. 返回新的 Promise
  return new Promise((resolve, reject) => {
    // 3. 遍历所有 Promise
    for (const promise of promises) {
      // 4. Promise.resolve 处理可能是普通值的情况
      Promise.resolve(promise).then(
        (value) => {
          // 5. 任何一个 Promise 成功,整体就 resolve
          //    这里是 race 的核心:谁先完成就返回谁
          resolve(value);
        },
        (reason) => {
          // 6. 任何一个 Promise 失败,整体就 reject
          reject(reason);
        }
      );
    }
  });
};

使用示例:

javascript 复制代码
const promises = [
  new Promise((r) => setTimeout(() => r(1), 1000)),
  new Promise((r) => setTimeout(() => r(2), 500)),
  new Promise((r) => setTimeout(() => r(3), 2000)),
];

Promise.myRace(promises).then(value => {
  console.log(value); // 2(第二个 Promise 最先完成)
});

// 应用:请求超时处理
const timeoutPromise = new Promise((_, reject) =>
  setTimeout(() => reject('请求超时'), 3000)
);

const requestPromise = fetch('https://api.example.com/data');

Promise.race([requestPromise, timeoutPromise])
  .then(data => console.log('获取成功', data))
  .catch(err => console.error('失败:', err));

2.4 Promise.allSettled 实现

javascript 复制代码
Promise.myAllSettled = function (promises) {
  promises = Array.isArray(promises) ? promises : [];

  return new Promise((resolve) => {
    // 处理空数组
    if (promises.length === 0) {
      resolve([]);
      return;
    }

    const results = [];
    let counter = 0;

    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(
        (value) => {
          // 成功情况:保存状态和值
          results[i] = { status: 'fulfilled', value };
        },
        (reason) => {
          // 失败情况:保存状态和原因
          results[i] = { status: 'rejected', reason };
        }
      ).finally(() => {
        // 不管成功还是失败,计数器都要 +1
        counter++;
        if (counter === promises.length) {
          resolve(results);
        }
      });
    }
  });
};

使用示例:

javascript 复制代码
const promises = [
  Promise.resolve(1),
  Promise.reject('错误'),
  Promise.resolve(3),
];

Promise.myAllSettled(promises).then(results => {
  console.log(results);
  // [
  //   { status: 'fulfilled', value: 1 },
  //   { status: 'rejected', reason: '错误' },
  //   { status: 'fulfilled', value: 3 }
  // ]
});

2.5 Promise.any 实现(ES2021)

javascript 复制代码
Promise.myAny = function (promises) {
  promises = Array.isArray(promises) ? promises : [];

  return new Promise((resolve, reject) => {
    // 处理空数组(AggregateError)
    if (promises.length === 0) {
      reject(new AggregateError([], 'All promises were rejected'));
      return;
    }

    const reasons = [];
    let counter = 0;

    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(
        (value) => {
          // 任何一个成功,整体就 resolve
          resolve(value);
        },
        (reason) => {
          // 收集失败原因
          reasons[i] = reason;
          counter++;

          // 所有都失败才 reject
          if (counter === promises.length) {
            reject(new AggregateError(reasons, 'All promises were rejected'));
          }
        }
      );
    }
  });
};

使用示例:

javascript 复制代码
const promises = [
  Promise.reject('错误1'),
  Promise.reject('错误2'),
  Promise.resolve(3),
];

Promise.myAny(promises).then(value => {
  console.log(value); // 3(有一个成功即可)
});

三、异步编程手写

3.1 Generator 函数实现

javascript 复制代码
// Generator 函数的状态机实现
function generatorToAsync(generator) {
  // 1. 返回一个 Promise
  return new Promise((resolve, reject) => {
    // 2. 执行 Generator,获取迭代器
    const gen = generator();

    // 3. 定义异步 step 函数,处理每一步
    function step(nextValue) {
      let result;
      try {
        // 4. 执行迭代器的 next,传入上一步的值
        result = gen.next(nextValue);
      } catch (error) {
        // 5. 如果执行出错,reject
        return reject(error);
      }

      // 6. 判断是否完成
      if (result.done) {
        // 7. Generator 执行完成,resolve 最终值
        return resolve(result.value);
      }

      // 8. 如果是 Promise,等待其完成
      //    这是实现 async/await 的关键
      Promise.resolve(result.value).then(
        (value) => step(value),
        (reason) => {
          // 9. 如果 Promise reject,抛出异常到 Generator
          try {
            gen.throw(reason);
          } catch (error) {
            reject(error);
          }
        }
      );
    }

    // 10. 开始执行 Generator
    step();
  });
}

使用示例:

javascript 复制代码
function* gen() {
  const a = yield Promise.resolve(1);
  console.log('a:', a);
  const b = yield Promise.resolve(2);
  console.log('b:', b);
  return a + b;
}

generatorToAsync(gen).then(result => {
  console.log('result:', result); // result: 3
});

3.2 实现红绿灯交换

javascript 复制代码
// 题目:使用 async/await 实现红绿灯交换
// 初始:let a = '红', b = '绿'
// 目标:a = '绿', b = '红'

async function swap() {
  const a = '红';
  const b = '绿';

  // 期望最终 a='绿', b='红'

  // 方法:利用临时变量交换
  const temp = a; // temp = '红'
  a = b;           // a = '绿'
  b = temp;        // b = '红'

  console.log(a, b); // 绿 红
}

// 手写版(不用临时变量,使用解构/数学运算/数组)
function swapNoTemp() {
  let a = '红';
  let b = '绿';

  // 方法1:解构赋值(现代写法)
  [a, b] = [b, a];
  console.log('解构:', a, b);

  // 重置
  a = '红'; b = '绿';

  // 方法2:数学运算(仅适用于数字)
  // a = a + b; // a = '红绿'
  // b = a.slice(0, a.length - b.length); // b = '红'
  // a = a.slice(b.length); // a = '绿'

  // 方法3:数组
  a = [b, b = a][0];
  console.log('数组:', a, b);
}

3.3 并发限制器实现(TaskScheduler)

javascript 复制代码
class TaskScheduler {
  constructor(concurrency) {
    // 1. 最大并发数
    this.concurrency = concurrency;
    // 2. 当前正在执行的任务数
    this.running = 0;
    // 3. 任务队列
    this.queue = [];
  }

  // 4. 添加任务
  addTask(task) {
    return new Promise((resolve, reject) => {
      // 5. 将任务包装,添加回调
      const wrappedTask = () => {
        this.running++;
        task()
          .then(resolve, reject)
          .finally(() => {
            // 6. 任务完成后,running - 1
            this.running--;
            // 7. 尝试执行下一个任务
            this.next();
          });
      };

      // 8. 如果当前没达到并发上限,立即执行
      //    否则加入队列等待
      if (this.running < this.concurrency) {
        wrappedTask();
      } else {
        this.queue.push(wrappedTask);
      }
    });
  }

  // 9. 执行下一个任务
  next() {
    if (this.queue.length > 0) {
      // 10. 从队列取出最早的任务执行
      const nextTask = this.queue.shift();
      nextTask();
    }
  }
}

使用示例:

javascript 复制代码
const scheduler = new TaskScheduler(2); // 最多同时运行 2 个

const tasks = [
  () => fetch('/api/1'),
  () => fetch('/api/2'),
  () => fetch('/api/3'),
  () => fetch('/api/4'),
];

// 添加任务
for (const task of tasks) {
  scheduler.addTask(task).then(() => {
    console.log('任务完成');
  });
}

四、手写 new 操作符与继承

4.1 多种继承方式实现

4.1.1 原型链继承
javascript 复制代码
function Parent() {
  this.name = 'Parent';
  this.colors = ['red', 'blue'];
}

Parent.prototype.sayHello = function () {
  return `Hello, I'm ${this.name}`;
};

function Child() {
  this.age = 25;
}

// 核心:将 Child 的原型指向 Parent 实例
// 这样 Child.prototype.__proto__ === Parent.prototype
// 实现了原型的继承
Child.prototype = new Parent();

// 别忘了修复 constructor 指向
Child.prototype.constructor = Child;

Child.prototype.sayAge = function () {
  return `I'm ${this.age} years old`;
};

const child = new Child();
console.log(child.name);       // Parent
console.log(child.colors);     // ['red', 'blue']
console.log(child.sayHello()); // Hello, I'm Parent
console.log(child.sayAge());   // I'm 25 years old

// 问题:引用类型共享
const c1 = new Child();
const c2 = new Child();
c1.colors.push('green');

console.log(c2.colors); // ['red', 'blue', 'green']
// 问题所在:所有实例共享同一个 colors 数组

4.1.2 构造函数继承(借用构造函数)
javascript 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

function Child(name, age) {
  // 核心:在 Child 内部调用 Parent
  // 这样可以将 Parent 的属性拷贝到 Child 实例上
  Parent.call(this, name); // 或 Parent.apply(this, arguments)
  this.age = age;
}

const child = new Child('Alice', 25);
console.log(child.name);   // Alice
console.log(child.colors); // ['red', 'blue']

// 优点:引用类型不共享,每个实例独立
const c1 = new Child('c1', 20);
const c2 = new Child('c2', 21);
c1.colors.push('green');
console.log(c2.colors); // ['red', 'blue'](不受影响)

// 缺点:
// 1. 方法不能继承(Parent.prototype 上的方法 Child 实例访问不到)
// 2. 每次调用都拷贝属性,性能损耗

4.1.3 组合继承(原型链 + 构造函数)
javascript 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

Parent.prototype.sayHello = function () {
  return `Hello, I'm ${this.name}`;
};

function Child(name, age) {
  // 1. 借用构造函数继承实例属性
  Parent.call(this, name);
  this.age = age;
}

// 2. 原型链继承方法
Child.prototype = new Parent();
// 3. 修复 constructor
Child.prototype.constructor = Child;

Child.prototype.sayAge = function () {
  return `I'm ${this.age}`;
};

const child = new Child('Alice', 25);

// 验证
console.log(child.name);      // Alice
console.log(child.colors);    // ['red', 'blue']
console.log(child.sayHello()); // Hello, I'm Alice
console.log(child.sayAge());  // I'm 25

// 解决了:
// - 引用类型共享问题(通过构造函数)
// - 方法继承问题(通过原型链)
// 缺点:调用了两次 Parent 构造函数(new Parent() 和 Parent.call())

4.1.4 原型式继承(Object.create 原理)
javascript 复制代码
function createObject(o) {
  // 1. 创建一个临时构造函数
  function Temp() {}

  // 2. 将构造函数的原型指向传入的对象
  Temp.prototype = o;

  // 3. 返回临时构造函数的实例
  return new Temp();
}

// 等价于 Object.create()
const parent = {
  name: 'Parent',
  friends: ['Alice', 'Bob']
};

const child = createObject(parent);
child.friends.push('Charlie');

console.log(parent.friends); // ['Alice', 'Bob', 'Charlie']
// 问题:引用类型共享

4.1.5 寄生式继承
javascript 复制代码
function createAnother(original) {
  // 1. 通过原型式继承创建对象
  const clone = Object.create(original);

  // 2. 增强对象(添加方法)
  clone.sayHi = function () {
    return `Hi, I'm ${this.name}`;
  };

  return clone;
}

const person = {
  name: 'Alice',
  friends: ['Bob', 'Charlie']
};

const person2 = createAnother(person);
console.log(person2.sayHi()); // Hi, I'm Alice

4.1.6 寄生组合继承(最佳方案)
javascript 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

Parent.prototype.sayHello = function () {
  return `Hello, I'm ${this.name}`;
};

function Child(name, age) {
  // 1. 借用构造函数继承实例属性
  Parent.call(this, name);
  this.age = age;
}

// 2. 原型式继承(不调用 Parent 构造函数)
// 这里使用一个临时函数 F,F.prototype = Parent.prototype
// 然后 Child.prototype = new F()
// 这样就绕过了 new Parent() 的调用
function inheritPrototype(Child, Parent) {
  const temp = function () {};
  temp.prototype = Parent.prototype;
  Child.prototype = new temp();
  Child.prototype.constructor = Child;
}

inheritPrototype(Child, Parent);

Child.prototype.sayAge = function () {
  return `I'm ${this.age}`;
};

const child = new Child('Alice', 25);

// 验证
console.log(child instanceof Child);  // true
console.log(child instanceof Parent); // true
console.log(child.constructor === Child); // true

// 寄生组合继承优点:
// 1. 只调用一次 Parent 构造函数
// 2. 原型链完整(instanceof 正确)
// 3. constructor 指向正确

4.1.7 ES6 Class 继承
javascript 复制代码
class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
  }

  sayHello() {
    return `Hello, I'm ${this.name}`;
  }
}

class Child extends Parent {
  constructor(name, age) {
    // 1. 必须先调用 super() 调用父类构造函数
    super(name); // 等价于 Parent.call(this, name)
    this.age = age;
  }

  sayAge() {
    return `I'm ${this.age}`;
  }
}

const child = new Child('Alice', 25);

// 验证
console.log(child instanceof Child);  // true
console.log(child instanceof Parent); // true
console.log(child.constructor === Child); // true

// extends 的原理:
// Child.prototype.__proto__ = Parent.prototype
// Child.__proto__ = Parent

五、手写常见数据结构

5.1 实现简易 EventEmitter

javascript 复制代码
class EventEmitter {
  constructor() {
    // 1. 使用对象存储事件和对应的回调函数
    // 结构:{ 'eventName': [callback1, callback2, ...] }
    this.events = {};
  }

  // 2. 订阅事件
  on(eventName, callback) {
    // 如果事件不存在,创建空数组
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    // 将回调添加到数组
    this.events[eventName].push(callback);
    // 返回 this,支持链式调用
    return this;
  }

  // 3. 只订阅一次
  once(eventName, callback) {
    // 创建一个包装函数,执行后自动 off
    const wrapper = (...args) => {
      callback(...args);
      this.off(eventName, wrapper);
    };
    return this.on(eventName, wrapper);
  }

  // 4. 触发事件
  emit(eventName, ...args) {
    // 获取该事件的所有回调
    const callbacks = this.events[eventName];
    if (!callbacks) return false;

    // 依次执行所有回调
    for (const callback of callbacks) {
      callback(...args);
    }
    return true;
  }

  // 5. 取消订阅
  off(eventName, callback) {
    const callbacks = this.events[eventName];
    if (!callbacks) return this;

    // 过滤掉要移除的回调
    this.events[eventName] = callbacks.filter(cb => cb !== callback);
    return this;
  }
}

使用示例:

javascript 复制代码
const emitter = new EventEmitter();

emitter.on('message', (content) => {
  console.log('收到消息:', content);
});

emitter.on('message', (content) => {
  console.log('处理消息:', content.toUpperCase());
});

emitter.emit('message', 'Hello'); // 触发两个回调

emitter.once('connect', () => {
  console.log('首次连接');
});

emitter.emit('connect'); // 第一次触发
emitter.emit('connect'); // 不触发(once 只生效一次)

emitter.off('message', /* 某个回调 */); // 移除特定回调

5.2 实现简易 Vue reactive

javascript 复制代码
function reactive(target) {
  // 1. 如果目标已经是响应式代理,直接返回(避免重复代理)
  if (target && target.__isReactive) {
    return target;
  }

  // 2. 获取已有的 handler 或使用默认的 Proxy handler
  const handlers = target.__proxyHandler || createHandlers();
  target.__proxyHandler = handlers;

  // 3. 创建并返回 Proxy 代理
  return new Proxy(target, handlers);
}

function createHandlers() {
  return {
    get(target, key, receiver) {
      // 1. 收集依赖
      // track(target, key);

      // 2. 返回值,如果是对象,递归代理(深度响应式)
      const value = Reflect.get(target, key, receiver);
      return typeof value === 'object' && value !== null
        ? reactive(value)
        : value;
    },

    set(target, key, value, receiver) {
      // 1. 检查是否真的有变化
      const oldValue = target[key];
      if (oldValue !== value) {
        // 2. 设置新值
        const result = Reflect.set(target, key, value, receiver);

        // 3. 触发更新
        // trigger(target, key);

        return result;
      }
      return true; // 即使没有变化也返回 true
    },

    deleteProperty(target, key) {
      const hasKey = target.hasOwnProperty(key);
      const result = Reflect.deleteProperty(target, key);

      if (hasKey && result) {
        // trigger(target, key, 'delete');
      }

      return result;
    }
  };
}

// 使用示例
const state = reactive({
  count: 0,
  user: { name: 'Alice' }
});

state.count++;           // 触发 set,自动收集依赖变化
state.user.name = 'Bob'; // 深层代理也能响应

5.3 实现简易 Redux

javascript 复制代码
// 1. 创建 store
function createStore(reducer, initialState) {
  let state = initialState;
  let listeners = [];

  // 获取状态
  function getState() {
    return state;
  }

  // 分发 action
  function dispatch(action) {
    // 2. 调用 reducer 计算新状态
    state = reducer(state, action);

    // 3. 通知所有订阅者
    listeners.forEach(listener => listener());
  }

  // 订阅状态变化
  function subscribe(listener) {
    listeners.push(listener);

    // 4. 返回取消订阅函数
    return () => {
      listeners = listeners.filter(l => l !== listener);
    };
  }

  // 初始化(分发一个默认 action)
  dispatch({ type: '@@INIT' });

  return { getState, dispatch, subscribe };
}

// 2. combineReducers 实现
function combineReducers(reducers) {
  // 3. 验证所有 reducer 都是函数
  const reducerKeys = Object.keys(reducers);
  reducers.forEach(reducer => {
    if (typeof reducer !== 'function') {
      throw new Error('Reducer must be a function');
    }
  });

  // 4. 返回组合后的 reducer
  return function combination(state = {}, action) {
    const nextState = {};

    // 5. 依次调用每个 reducer
    reducerKeys.forEach(key => {
      const reducer = reducers[key];
      const previousStateForKey = state[key];
      nextState[key] = reducer(previousStateForKey, action);
    });

    // 6. 返回完整的状态对象
    return nextState;
  };
}

使用示例:

javascript 复制代码
// 定义 reducer
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

// 创建 store
const store = createStore(counterReducer);

// 订阅变化
store.subscribe(() => {
  console.log('状态变化:', store.getState());
});

// 分发 action
store.dispatch({ type: 'INCREMENT' }); // { count: 1 }
store.dispatch({ type: 'INCREMENT' }); // { count: 2 }
store.dispatch({ type: 'DECREMENT' }); // { count: 1 }

六、手写工具函数

6.1 JSONP 实现

javascript 复制代码
function jsonp(url, params = {}, callbackName = 'callback') {
  return new Promise((resolve, reject) => {
    // 1. 生成唯一的回调函数名
    const callbackId = `jsonp_${Date.now()}_${Math.random().toString(36).substr(2)}`;

    // 2. 构建查询参数
    const queryParams = { ...params, [callbackName]: callbackId };
    const queryString = Object.entries(queryParams)
      .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
      .join('&');

    // 3. 拼接完整 URL
    const fullUrl = `${url}${url.includes('?') ? '&' : '?'}${queryString}`;

    // 4. 创建 script 标签
    const script = document.createElement('script');
    script.src = fullUrl;

    // 5. 全局回调函数
    window[callbackId] = (data) => {
      // 清理
      delete window[callbackId];
      document.body.removeChild(script);
      resolve(data);
    };

    // 6. 错误处理
    script.onerror = (error) => {
      delete window[callbackId];
      document.body.removeChild(script);
      reject(error);
    };

    // 7. 发送请求
    document.body.appendChild(script);
  });
}

// 使用
jsonp('https://api.example.com/data', { id: 123 })
  .then(data => console.log(data))
  .catch(err => console.error(err));

6.2 手写简易 axios

javascript 复制代码
function axios(config) {
  // 1. 处理配置默认值
  const { url = '', method = 'GET', params = {}, data = null, headers = {} } = config;

  // 2. 构建 URL 参数
  const queryString = Object.entries(params)
    .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
    .join('&');

  // 3. 完整 URL
  const fullUrl = method === 'GET' && queryString
    ? `${url}${url.includes('?') ? '&' : '?'}${queryString}`
    : url;

  // 4. 返回 Promise
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    // 5. 打开连接
    xhr.open(method, fullUrl, true);

    // 6. 设置请求头
    Object.entries(headers).forEach(([key, value]) => {
      xhr.setRequestHeader(key, value);
    });

    // 7. 监听状态变化
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          // 8. 成功处理
          const response = {
            data: JSON.parse(xhr.responseText),
            status: xhr.status,
            statusText: xhr.statusText
          };
          resolve(response);
        } else {
          // 9. 错误处理
          reject(new Error(`Request failed with status ${xhr.status}`));
        }
      }
    };

    // 10. 处理请求错误
    xhr.onerror = () => reject(new Error('Network Error'));

    // 11. 发送请求
    if (method === 'GET') {
      xhr.send();
    } else {
      xhr.send(data ? JSON.stringify(data) : null);
    }
  });
}

// 使用
axios({
  url: 'https://api.example.com/users',
  method: 'GET',
  params: { page: 1, limit: 10 }
}).then(res => console.log(res.data))
  .catch(err => console.error(err));

axios({
  url: 'https://api.example.com/users',
  method: 'POST',
  data: { name: 'Alice', age: 25 },
  headers: { 'Content-Type': 'application/json' }
}).then(res => console.log(res.data));

6.3 实现 LRU Cache

javascript 复制代码
class LRUCache {
  constructor(maxSize = 10) {
    // 1. 最大容量
    this.maxSize = maxSize;
    // 2. 使用 Map 保持顺序(Map 的迭代顺序是插入顺序)
    //    同时可以 O(1) 访问和删除
    this.cache = new Map();
  }

  // 3. 获取值,同时将其移到最新位置
  get(key) {
    if (!this.cache.has(key)) {
      return null; // 不存在
    }

    // 1) 获取值
    const value = this.cache.get(key);

    // 2) 删除(为了重新插入到最新位置)
    this.cache.delete(key);

    // 3) 重新插入(变成最近使用的)
    this.cache.set(key, value);

    return value;
  }

  // 4. 设置值
  set(key, value) {
    // 如果已存在,先删除
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.maxSize) {
      // 如果容量已满,删除最老的(Map 的第一个元素)
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }

    // 插入到最新位置
    this.cache.set(key, value);
  }

  // 5. 删除指定 key
  delete(key) {
    this.cache.delete(key);
  }

  // 6. 清空
  clear() {
    this.cache.clear();
  }

  // 7. 获取缓存信息
  getCache() {
    return Array.from(this.cache.entries());
  }
}

使用示例:

javascript 复制代码
const cache = new LRUCache(3);

cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);

console.log(cache.get('a')); // 1(a 变成最近使用的)
console.log(cache.getCache()); // [['b', 2], ['c', 3], ['a', 1]]

cache.set('d', 4); // 超出容量,删除最老的 'b'
console.log(cache.getCache()); // [['c', 3], ['a', 1], ['d', 4]]

6.4 实现简易 Router

javascript 复制代码
class Router {
  constructor() {
    // 1. 路由映射
    this.routes = {};
    // 2. 当前路径
    this.currentPath = window.location.pathname || '/';
    // 3. 绑定 popstate 事件(浏览器前进后退)
    window.addEventListener('popstate', () => {
      this.currentPath = window.location.pathname;
      this.handleRoute();
    });
  }

  // 4. 注册路由
  register(path, handler) {
    this.routes[path] = handler;
  }

  // 5. 跳转到指定路径
  push(path) {
    // 使用 history.pushState 不会触发 popstate
    window.history.pushState(null, '', path);
    this.currentPath = path;
    this.handleRoute();
  }

  // 6. 处理路由匹配
  handleRoute() {
    const path = this.currentPath;

    // 精确匹配优先
    if (this.routes[path]) {
      this.routes[path]();
      return;
    }

    // 动态路由匹配(如 /user/:id)
    for (const routePath of Object.keys(this.routes)) {
      const paramNames = [];
      const regexPath = routePath.replace(/:([^/]+)/g, (_, name) => {
        paramNames.push(name);
        return '([^/]+)';
      });

      const regex = new RegExp(`^${regexPath}$`);
      const match = path.match(regex);

      if (match) {
        const params = {};
        paramNames.forEach((name, index) => {
          params[name] = match[index + 1];
        });
        this.routes[routePath](params);
        return;
      }
    }

    // 404 处理
    console.warn('Route not found:', path);
  }
}

使用示例:

javascript 复制代码
const router = new Router();

router.register('/', () => {
  console.log('首页');
  document.body.innerHTML = '<h1>首页</h1>';
});

router.register('/user/:id', (params) => {
  console.log('用户详情:', params.id);
  document.body.innerHTML = `<h1>用户 ${params.id} 的详情</h1>`;
});

router.register('/about', () => {
  console.log('关于');
  document.body.innerHTML = '<h1>关于</h1>';
});

// 页面加载时处理当前路径
router.handleRoute();

// 导航
document.getElementById('homeBtn').onclick = () => router.push('/');
document.getElementById('userBtn').onclick = () => router.push('/user/123');

七、手写算法题

7.1 快速排序

javascript 复制代码
function quickSort(arr) {
  // 1. 递归终止条件:数组长度 <= 1
  if (arr.length <= 1) {
    return arr;
  }

  // 2. 选择基准元素(这里选中间位置)
  const pivotIndex = Math.floor(arr.length / 2);
  const pivot = arr[pivotIndex];

  // 3. 分区:左边小于基准,右边大于基准
  const left = [];
  const right = [];

  for (let i = 0; i < arr.length; i++) {
    // 跳过基准元素本身
    if (i === pivotIndex) continue;

    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }

  // 4. 递归排序左右两部分,然后拼接
  //    [排序后的左半部分] + [基准] + [排序后的右半部分]
  return quickSort(left).concat(pivot, quickSort(right));
}

// 原地排序版本(空间复杂度 O(1))
function quickSortInPlace(arr, low = 0, high = arr.length - 1) {
  if (low < high) {
    // 1. 分区,获取基准的正确位置
    const pivotIndex = partition(arr, low, high);

    // 2. 递归排序左右两部分
    quickSortInPlace(arr, low, pivotIndex - 1);
    quickSortInPlace(arr, pivotIndex + 1, high);
  }

  return arr;
}

function partition(arr, low, high) {
  // 2. 选择基准(这里选最右边)
  const pivot = arr[high];

  // 3. i 用于记录小于基准的元素的最后位置
  let i = low - 1;

  for (let j = low; j < high; j++) {
    if (arr[j] <= pivot) {
      i++;
      // 交换 arr[i] 和 arr[j]
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
  }

  // 4. 将基准放到正确位置(i + 1)
  [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];

  return i + 1;
}

7.2 归并排序

javascript 复制代码
function mergeSort(arr) {
  // 1. 递归终止条件
  if (arr.length <= 1) {
    return arr;
  }

  // 2. 分成两半
  const mid = Math.floor(arr.length / 2);
  const left = arr.slice(0, mid);
  const right = arr.slice(mid);

  // 3. 递归排序左右两部分
  const sortedLeft = mergeSort(left);
  const sortedRight = mergeSort(right);

  // 4. 合并两个有序数组
  return merge(sortedLeft, sortedRight);
}

function merge(left, right) {
  // 1. 结果数组
  const result = [];

  // 2. 双指针遍历
  let i = 0, j = 0;

  while (i < left.length && j < right.length) {
    if (left[i] <= right[j]) {
      result.push(left[i]);
      i++;
    } else {
      result.push(right[j]);
      j++;
    }
  }

  // 3. 剩余元素直接追加(其中一个数组已经全部放完)
  while (i < left.length) {
    result.push(left[i]);
    i++;
  }

  while (j < right.length) {
    result.push(right[j]);
    j++;
  }

  return result;
}

7.3 二分查找

javascript 复制代码
// 非递归版本
function binarySearch(arr, target) {
  let left = 0;
  let right = arr.length - 1;

  while (left <= right) {
    // 1. 计算中间位置(防止溢出)
    const mid = Math.floor(left + (right - left) / 2);

    if (arr[mid] === target) {
      return mid; // 找到目标,返回索引
    } else if (arr[mid] < target) {
      // 2. 目标在右半部分
      left = mid + 1;
    } else {
      // 3. 目标在左半部分
      right = mid - 1;
    }
  }

  // 4. 未找到,返回 -1
  return -1;
}
javascript 复制代码
// 递归版本
function binarySearchRecursive(arr, target, left = 0, right = arr.length - 1) {
  // 1. 递归终止条件
  if (left > right) {
    return -1;
  }

  // 2. 计算中间位置
  const mid = Math.floor(left + (right - left) / 2);

  if (arr[mid] === target) {
    return mid;
  } else if (arr[mid] < target) {
    // 3. 搜索右半部分
    return binarySearchRecursive(arr, target, mid + 1, right);
  } else {
    // 4. 搜索左半部分
    return binarySearchRecursive(arr, target, left, mid - 1);
  }
}

使用示例:

javascript 复制代码
const arr = [1, 3, 5, 7, 9, 11, 13, 15];
console.log(binarySearch(arr, 7));   // 3
console.log(binarySearch(arr, 6));   // -1

7.4 链表操作

7.4.1 链表节点定义
javascript 复制代码
class ListNode {
  constructor(val = 0, next = null) {
    this.val = val;     // 节点值
    this.next = next;   // 指向下一个节点的指针
  }
}
7.4.2 反转链表
javascript 复制代码
// 方法一:迭代(推荐)
function reverseList(head) {
  // 1. 前一个节点
  let prev = null;
  // 2. 当前节点
  let curr = head;

  while (curr !== null) {
    // 3. 保存下一个节点
    const next = curr.next;

    // 4. 反转指针指向
    curr.next = prev;

    // 5. 移动 prev 和 curr
    prev = curr;
    curr = next;
  }

  // 6. prev 就是新的头节点
  return prev;
}

// 方法二:递归
function reverseListRecursive(head) {
  // 1. 递归终止条件
  if (head === null || head.next === null) {
    return head;
  }

  // 2. 递归反转后续链表
  const newHead = reverseListRecursive(head.next);

  // 3. 反转当前节点和下一个节点的指针
  head.next.next = head;
  head.next = null;

  return newHead;
}
7.4.3 链表环检测
javascript 复制代码
function hasCycle(head) {
  // 1. 使用快慢指针
  let slow = head; // 每次走一步
  let fast = head; // 每次走两步

  while (fast !== null && fast.next !== null) {
    slow = slow.next;
    fast = fast.next.next;

    // 2. 如果有环,快慢指针一定会相遇
    if (slow === fast) {
      return true;
    }
  }

  // 3. fast 走到末尾,说明无环
  return false;
}

// 找到环的入口点
function detectCycle(head) {
  let slow = head;
  let fast = head;

  while (fast !== null && fast.next !== null) {
    slow = slow.next;
    fast = fast.next.next;

    if (slow === fast) {
      // 4. 找到相遇点后,让 slow 回到头节点
      //    然后快慢指针再次相遇,就是环的入口
      slow = head;
      while (slow !== fast) {
        slow = slow.next;
        fast = fast.next;
      }
      return slow;
    }
  }

  return null;
}

7.5 合并两个有序链表

javascript 复制代码
function mergeTwoLists(l1, l2) {
  // 1. 创建虚拟头节点,简化边界处理
  const dummy = new ListNode(-1);

  // 2. 当前指针
  let curr = dummy;

  // 3. 遍历两个链表
  while (l1 !== null && l2 !== null) {
    if (l1.val <= l2.val) {
      curr.next = l1;
      l1 = l1.next;
    } else {
      curr.next = l2;
      l2 = l2.next;
    }
    curr = curr.next;
  }

  // 4. 连接剩余部分
  curr.next = l1 !== null ? l1 : l2;

  // 5. 返回虚拟头节点的下一个(真正的头节点)
  return dummy.next;
}

// 递归版本
function mergeTwoListsRecursive(l1, l2) {
  // 1. 递归终止条件
  if (l1 === null) return l2;
  if (l2 === null) return l1;

  // 2. 比较头节点,小的作为新的头
  if (l1.val <= l2.val) {
    l1.next = mergeTwoListsRecursive(l1.next, l2);
    return l1;
  } else {
    l2.next = mergeTwoListsRecursive(l1, l2.next);
    return l2;
  }
}

7.6 实现深拷贝(带循环引用)

javascript 复制代码
function deepClone(target, hash = new WeakMap()) {
  // 1. 处理非对象
  if (typeof target !== 'object' || target === null) {
    return target;
  }

  // 2. 处理循环引用
  if (hash.has(target)) {
    return hash.get(target);
  }

  // 3. 处理 Date
  if (target instanceof Date) {
    return new Date(target);
  }

  // 4. 处理 RegExp
  if (target instanceof RegExp) {
    return new RegExp(target.source, target.flags);
  }

  // 5. 处理函数
  if (typeof target === 'function') {
    // 函数特殊处理:返回一个新函数
    return function (...args) {
      return target.apply(this, args);
    };
  }

  // 6. 处理数组和对象
  const cloneTarget = Array.isArray(target) ? [] : {};

  // 7. 记录到 hash(用于处理循环引用)
  hash.set(target, cloneTarget);

  // 8. 遍历属性,递归拷贝
  for (const key of Reflect.ownKeys(target)) {
    cloneTarget[key] = deepClone(target[key], hash);
  }

  return cloneTarget;
}

7.7 LRU 算法实现

javascript 复制代码
class LRUCache {
  constructor(capacity) {
    // 1. 缓存容量
    this.capacity = capacity;
    // 2. 使用 Map,迭代顺序即访问顺序
    //    最新的在最后,最老的在最前
    this.cache = new Map();
  }

  // 2. 获取缓存
  get(key) {
    if (!this.cache.has(key)) {
      return -1;
    }

    // 1) 获取值
    const value = this.cache.get(key);

    // 2) 删除并重新插入(移到最新位置)
    this.cache.delete(key);
    this.cache.set(key, value);

    return value;
  }

  // 3. 设置缓存
  put(key, value) {
    if (this.cache.has(key)) {
      // 如果已存在,删除(准备重新插入)
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      // 如果满了,删除最老的(Map 的第一个键)
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }

    // 插入到最新位置
    this.cache.set(key, value);
  }
}

八、综合性手写题

8.1 实现 compose 函数

javascript 复制代码
// compose:将多个函数组合成一个,从右到左执行
function compose(...fns) {
  // 1. 参数校验
  if (fns.length === 0) {
    return (arg) => arg; // 返回恒等函数
  }

  if (fns.length === 1) {
    return fns[0];
  }

  // 2. 从右到左组合函数
  return function (initValue) {
    // 3. 从右到左执行,每个函数的输出作为下一个函数的输入
    return fns.reduceRight((acc, fn) => {
      return fn(acc);
    }, initValue);
  };
}

// pipe:与 compose 类似,但从左到右执行
function pipe(...fns) {
  if (fns.length === 0) {
    return (arg) => arg;
  }

  if (fns.length === 1) {
    return fns[0];
  }

  return function (initValue) {
    return fns.reduce((acc, fn) => {
      return fn(acc);
    }, initValue);
  };
}

使用示例:

javascript 复制代码
const add10 = x => x + 10;
const multiply2 = x => x * 2;
const subtract5 = x => x - 5;

// compose:从右到左
// 等价于:add10(multiply2(subtract5(10)))
const composed = compose(add10, multiply2, subtract5);
console.log(composed(10)); // (10 - 5) * 2 + 10 = 20

// pipe:从左到右
// 等价于:(((10 + 10) * 2) - 5) = 35
const piped = pipe(add10, multiply2, subtract5);
console.log(piped(10)); // 35

8.2 实现 curry 函数(高级版)

javascript 复制代码
function curry(fn) {
  // 1. 获取函数参数个数
  const arity = fn.length;

  // 2. 内部函数,保存已收集的参数
  return function curried(...args) {
    // 3. 如果收集的参数够了,执行原函数
    if (args.length >= arity) {
      return fn.apply(this, args);
    }

    // 4. 否则,返回一个新函数继续收集参数
    return function (...nextArgs) {
      // 5. 递归收集参数
      return curried.apply(this, [...args, ...nextArgs]);
    };
  };
}

// 带占位符的高级版本
function curryWithPlaceholder(fn) {
  const arity = fn.length;
  const placeholder = Symbol('placeholder');

  function curried(...args) {
    // 1. 计算实际参数个数(占位符不计入)
    const actualArgs = args.filter(a => a !== placeholder);

    // 2. 如果收集够了,执行
    if (actualArgs.length >= arity) {
      // 3. 替换占位符为实际值
      const finalArgs = args.map(a =>
        a === placeholder ? actualArgs.shift() : a
      );
      return fn.apply(this, finalArgs);
    }

    return function (...nextArgs) {
      // 4. 合并参数,保留占位符
      return curried.apply(this, [...args.map(a =>
        a === placeholder && nextArgs.length > 0
          ? nextArgs.shift()
          : a
      ), ...nextArgs]);
    };
  }

  return curried;
}

使用示例:

javascript 复制代码
const join = (a, b, c) => `${a}-${b}-${c}`;
const curriedJoin = curry(join);

console.log(curriedJoin(1)(2)(3));        // 1-2-3
console.log(curriedJoin(1, 2)(3));       // 1-2-3
console.log(curriedJoin(1)(2, 3));       // 1-2-3

// 带占位符
const partialJoin = curryWithPlaceholder(join);
console.log(partialJoin(1, 2)(3));       // 1-2-3
console.log(partialJoin(1, placeholder)(3)); // 1-undefined-3 (占位符被替换)

8.3 实现 memoize 函数

javascript 复制代码
// 基础的记忆化函数
function memoize(fn) {
  // 1. 使用 Map 缓存结果,key 可以是任意类型
  const cache = new Map();

  return function (...args) {
    // 2. 生成缓存 key(使用 JSON 序列化)
    const key = JSON.stringify(args);

    // 3. 检查是否有缓存
    if (cache.has(key)) {
      return cache.get(key);
    }

    // 4. 没有缓存,执行函数并缓存结果
    const result = fn.apply(this, args);
    cache.set(key, result);

    return result;
  };
}

// 带过期时间的记忆化
function memoizeWithTTL(fn, ttl = 60000) {
  const cache = new Map();

  return function (...args) {
    const key = JSON.stringify(args);
    const now = Date.now();
    const entry = cache.get(key);

    // 1. 检查缓存是否存在且未过期
    if (entry && (now - entry.timestamp) < ttl) {
      return entry.value;
    }

    // 2. 执行并缓存
    const result = fn.apply(this, args);
    cache.set(key, { value: result, timestamp: now });

    return result;
  };
}

// 支持对象 key 的记忆化
function memoizeWithKey(fn, keyGenerator = JSON.stringify) {
  const cache = new Map();

  return function (...args) {
    const key = keyGenerator.apply(this, args);

    if (cache.has(key)) {
      return cache.get(key);
    }

    const result = fn.apply(this, args);
    cache.set(key, result);

    return result;
  };
}

使用示例:

javascript 复制代码
// 斐波那契数列(带记忆化)
const fibonacci = memoize(function(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

console.time('fib');
console.log(fibonacci(100)); // 很快,因为有缓存
console.timeEnd('fib');

// 带 TTL 的 API 缓存
const fetchUser = memoizeWithTTL(async (userId) => {
  const response = await fetch(`/api/user/${userId}`);
  return response.json();
}, 60000); // 60秒过期

fetchUser(1).then(user => console.log(user));

8.4 实现简易 Promise.series(串行执行异步任务)

javascript 复制代码
// 串行执行一系列返回 Promise 的函数
function promiseSeries(tasks) {
  // 1. 初始值是一个 resolved 的 Promise
  const result = [];

  return tasks.reduce((promise, task, index) => {
    return promise.then(() => {
      // 2. 执行当前任务
      return task().then(res => {
        // 3. 保存结果
        result[index] = res;
      });
    });
  }, Promise.resolve()).then(() => result);
}

// 更简洁的版本
function promiseSeriesSimple(tasks) {
  const results = [];

  return new Promise((resolve, reject) => {
    let index = 0;

    function next() {
      if (index >= tasks.length) {
        resolve(results);
        return;
      }

      tasks[index++]()
        .then(res => {
          results.push(res);
          next();
        })
        .catch(reject);
    }

    next();
  });
}

使用示例:

javascript 复制代码
const tasks = [
  () => new Promise(r => setTimeout(() => { console.log('1'); r(1); }, 100)),
  () => new Promise(r => setTimeout(() => { console.log('2'); r(2); }, 50)),
  () => new Promise(r => setTimeout(() => { console.log('3'); r(3); }, 150)),
];

promiseSeries(tasks).then(results => {
  console.log('所有任务完成:', results); // [1, 2, 3]
});

九、高频手写代码清单

必背清单

序号 题目 难度 备注
1 call/apply/bind ⭐⭐ 核心理解 this 绑定
2 new 操作符 ⭐⭐ 构造函数执行流程
3 防抖/节流 ⭐⭐ 实际开发高频
4 深拷贝 ⭐⭐⭐ 循环引用处理
5 Promise 实现 ⭐⭐⭐ 异步编程核心
6 Promise.all/race ⭐⭐ 异步并发控制
7 数组扁平化 多种方法
8 数组去重 Set/filter/map
9 继承(组合/寄生) ⭐⭐⭐ 原型链理解
10 instanceof 原型链遍历
11 柯里化 ⭐⭐ 函数式编程
12 浅拷贝/深拷贝 ⭐⭐ 引用类型处理
13 EventEmitter ⭐⭐ 事件发布订阅
14 LRU Cache ⭐⭐⭐ 数据结构+算法
15 快排/归并 ⭐⭐ 算法基础
16 二分查找 ⭐⭐ 算法基础
17 链表反转 ⭐⭐ 数据结构基础
18 链表环检测 ⭐⭐ 快慢指针
19 合并有序链表 ⭐⭐ 合并操作
20 compose/pipe ⭐⭐ 函数组合

10.2 代码模板

javascript 复制代码
/**
 * 函数描述
 * @param {type} paramName - 参数描述
 * @return {type} 返回值描述
 */
function solution(param) {
  // 1. 边界情况处理
  if (!param) return defaultValue;

  // 2. 算法核心逻辑
  const result = param.map(item => {
    // 处理每个元素
    return transform(item);
  });

  // 3. 返回结果
  return result;
}

10.3 常见错误

  • 循环引用没处理
  • this 指向丢失
  • 异步回调中 this 绑错
  • 边界条件漏掉(空数组、null)
  • 原型链继承 constructor 忘记修复
  • Promise 状态不可逆忘记
  • 引用类型共享(浅拷贝问题)
相关推荐
李白的天不白1 小时前
大规模请求数据并发问题
java·前端·数据库
冲浪中台2 小时前
【无标题】
前端·低代码
openKaka_2 小时前
beginWork 的第一站:HostRoot 如何把 App 接入 Fiber 树
前端·javascript·react.js
我命由我123452 小时前
Dart - Dart SDK、Hello World 案例、变量声明、常量声明、常量 final、字符串类型
前端·flutter·前端框架·html·web·dart·web app
冴羽yayujs2 小时前
GitHub 前端热榜项目 - 日榜(2026-05-11)
前端·github
~|Bernard|2 小时前
四,go语言中GMP调度模型
java·前端·golang
YOU OU2 小时前
HTML+CSS+JavaScript
前端·javascript·css·html
Rkgua2 小时前
路径传参和查询传参和请求体传参区以及Vue和React的用法区分
前端·面试
JarvanMo2 小时前
Flutter + Supabase 集成 Apple Sign-In 完整指南
前端