前端手写代码大全
一、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 状态不可逆忘记
- 引用类型共享(浅拷贝问题)