🔥 核心手写题解析
1. 防抖与节流函数
应用场景:搜索框输入联想、滚动事件优化
// 防抖:多次触发只执行最后一次[5](@ref)
function debounce(func, delay) {
let timer;
return function (...args) {
clearTimeout(timer); // 清除旧定时器
timer = setTimeout(() => {
func.apply(this, args); // 保留 this 指向
}, delay);
};
}
// 节流:固定时间间隔执行[5](@ref)
function throttle(func, delay) {
let timer;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, args);
timer = null; // 重置状态
}, delay);
}
};
}
2. 柯里化函数实现
核心思想:分步传递参数,实现函数复用
function curry(fn) {
return function curried(...args) {
// 参数足够时执行,否则返回新函数
return args.length >= fn.length
? fn.apply(this, args)
: (...newArgs) => curried(...args, ...newArgs);
};
}
// 示例:支持多参数累加
const curryAdd = curry((a, b, c) => a + b + c);
console.log(curryAdd(1)(2)(3)); // 6
3. 原型链方法实现
3.1 call/apply/bind 三兄弟
// call:逐个传递参数
Function.prototype.myCall = function (context, ...args) {
context = context || window;
const fnSymbol = Symbol('fn');
context[fnSymbol] = this; // this 指向调用函数
const res = context[fnSymbol](...args);
delete context[fnSymbol];
return res;
};
// apply:数组形式传参[5](@ref)
Function.prototype.myApply = function (context, args) {
// 实现逻辑类似 call,需判断参数是否为数组
};
// bind:返回绑定 this 的新函数
// bind 改变this并返回一个新函数,参数为逗号分隔的参数列表,支持new操作符
Function.prototype.myBind = function (context, ...args) {
const self = this;
function BoundFunction(...newArgs) {
const isNew = new.target !== undefined;
const isT = isNew ? this : context;
let countArgs = args.concat(newArgs);
return self._excute(isT, countArgs);
}
function F() {}
F.prototype = self.prototype;
self._excute = function (c, a) {
const fnSymbol = Symbol("fn");
c[fnSymbol] = self;
const res = c[fnSymbol](...a);
delete c[fnSymbol];
return res;
};
return BoundFunction;
};
4. 深拷贝终极方案
解决痛点:循环引用、特殊对象处理
function deepClone(obj, hash = new WeakMap()) {
if (!obj || typeof obj !== "object") {
return obj;
}
if (hash.get(obj)) return hash.get(obj);
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
const clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (const item in obj) {
if (obj.hasOwnProperty(item)) {
clone[item] = deepClone(obj[item], hash);
}
}
return clone;
}
🚀 进阶手写题精选
5. EventBus 事件总线
功能亮点:支持 once/off/异步触发
class EventBus {
constructor() {
this.events = new Map();
}
on(eventName, callback) {
if (!this.events.has(eventName)) {
this.events.set(eventName, []);
}
this.events.get(eventName).push(callback);
return () => this.off(eventName, callback);
}
off(eventName, callback) {
const event = this.events.get(eventName);
if (event) {
this.events.set(
eventName,
event.filter((fn) => fn !== callback)
);
}
}
emit(eventName, ...args) {
const event = this.events.get(eventName);
if (event) {
event.forEach((fn) => {
setTimeout(() => {
fn(...args);
}, 0);
});
}
}
once(eventName, callback) {
const onWrapper = (...args) => {
callback(...args);
this.off(eventName, onWrapper);
};
return this.on(eventName, onWrapper); // 返回一个取消监听的函数,方便后续调用
}
}
6. 倒计时精准控制
核心方案:解决客户端时间误差问题
class countDown {
constructor(endTime, serverTime, endCallback) {
this.endTime = new Date(endTime).getTime();
this.serverTime = new Date(serverTime).getTime();
this.clientTime = Date.now();
this.timeDiff = this.serverTime - this.clientTime;
this.animationFrameId = null;
this.timeRun = false;
this.endCallback = endCallback;
}
start() {
if (this.timeRun) return;
this.timeRun = true;
const update = () => {
let now = Date.now() + this.timeDiff;
let timeRemind = this.endTime - now;
console.log(timeRemind); // 打印剩余时间,单位为毫秒
if (timeRemind <= 0) {
this.stop();
this.endCallback?.();
return;
}
const day = Math.floor(timeRemind / (1000 * 60 * 60 * 24));
timeRemind %= 1000 * 60 * 60 * 24;
const hour = Math.floor(timeRemind / (1000 * 60 * 60));
timeRemind %= 1000 * 60 * 60;
const minute = Math.floor(timeRemind / (1000 * 60));
timeRemind %= 1000 * 60;
const second = Math.floor(timeRemind / 1000);
console.log(`${day}天${hour}时${minute}分${second}秒`);
this.animationFrameId = requestAnimationFrame(update);
};
this.animationFrameId = requestAnimationFrame(update);
}
stop() {
cancelAnimationFrame(this.animationFrameId);
this.timeRun = false;
}
}
6. 任务重试与最大失败次数控制
核心方案:事件失败触发重试,最大次数之后抛出异常
async function executeTasks(tasks, retries) {
const result = [];
for (const task of tasks) {
let errCount = 0,
lastErr = null;
while (errCount <= retries) {
try {
const res = await task();
result.push(res);
break;
} catch (error) {
lastErr = error;
errCount++;
if (errCount > retries) {
throw lastErr;
}
}
}
}
return result;
}
7. 实现一个链式调用
class Cat {
#taskQueue = [];
constructor() {
Promise.resolve().then(() => this.#runQueue());
}
async #runQueue() {
for (const task of this.#taskQueue) {
await task();
}
}
sleep(time) {
this.#taskQueue.push(() => {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
});
return this;
}
bye(msg) {
this.#taskQueue.push(() => {
return new Promise((resolve) => {
console.log(msg);
resolve();
});
});
return this;
}
say(msg) {
this.#taskQueue.push(() => {
return new Promise((resolve) => {
console.log(msg);
resolve();
});
});
return this;
}
}
const cat = new Cat();
cat.say("Hello").sleep(3000).bye("bye");
8. map语法糖功能实现
Array.prototype.myMap = function (callback) {
const result = [];
for (let i = 0, len = this.length; i < len; ++i) {
if (this.hasOwnProperty(i)) {
result.push(callback(this[i], i, this));
}
}
return result;
};
9. Instance语法糖功能实现
function myInstance(obj, constructor) {
if (!obj || typeof obj !== "object") return false;
if (
typeof constructor.prototype !== "function" ||
constructor.prototype === undefined
)
return false;
let curProto = Object.getPrototypeOf(obj);
while (curProto !== null) {
if (curProto === constructor.prototype) {
return true;
}
curProto = Object.getPrototypeOf(curProto);
}
return false;
}