前言
日常开发中,我们经常会用到各种工具函数,比如防抖、节流、Promise、柯里化等。了解它们的实现原理,不仅能提升代码能力,还能更好地在实际场景中灵活运用。本文将手把手带你实现这些常见的工具函数。
1. 防抖(Debounce)
防抖的核心思想是:高频触发的事件,在指定时间内只执行最后一次。
基本实现
function debounce(fn, delay) {
let timer = null;
return function(...args) {
const context = this;
// 如果已有定时器,先清除
if (timer) clearTimeout(timer);
// 设置新的定时器
timer = setTimeout(() => {
fn.apply(context, args);
timer = null;
}, delay);
};
}
立即执行版本
function debounceImmediate(fn, delay, immediate = true) {
let timer = null;
return function(...args) {
const context = this;
if (timer) clearTimeout(timer);
if (immediate && !timer) {
fn.apply(context, args);
}
timer = setTimeout(() => {
if (!immediate) {
fn.apply(context, args);
}
timer = null;
}, delay);
};
}
使用场景
- 搜索框输入防抖
- 窗口 resize
- 表单验证
2. 节流(Throttle)
节流的核心思想是:高频触发的事件,按照固定时间间隔执行。
时间戳实现
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const context = this;
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(context, args);
lastTime = now;
}
};
}
定时器实现
function throttleTimer(fn, interval) {
let timer = null;
return function(...args) {
const context = this;
if (!timer) {
timer = setTimeout(() => {
fn.apply(context, args);
timer = null;
}, interval);
}
};
}
使用场景
- 滚动事件处理
- 按钮点击防重复提交
- 游戏中的帧率控制
3. Promise 实现
Promise 是异步编程的解决方案,让我们手写一个简易版 Promise。
基础版
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(cb => cb(value));
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.value = reason;
this.onRejectedCallbacks.forEach(cb => cb(reason));
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handle = (callback, value) => {
try {
const result = callback ? callback(value) : value;
resolve(result);
} catch (error) {
reject(error);
}
};
if (this.state === 'fulfilled') {
handle(onFulfilled, this.value);
} else if (this.state === 'rejected') {
handle(onRejected, this.value);
} else {
this.onFulfilledCallbacks.push(() => handle(onFulfilled, this.value));
this.onRejectedCallbacks.push(() => handle(onRejected, this.value));
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
static resolve(value) {
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value;
count++;
if (count === promises.length) {
resolve(results);
}
},
reject
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
}
}
4. 柯里化(Curry)
柯里化是把接受多个参数的函数转换成接受一个单一参数的函数。
基本实现
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
};
}
优化版本(支持占位符)
function curry(fn, placeholder = '_') {
return function curried(...args) {
const newArgs = args.map(arg =>
arg === placeholder && this.lastArg ? this.lastArg : arg
);
if (newArgs.filter(arg => arg !== placeholder).length >= fn.length) {
return fn.apply(this, newArgs);
}
curried.lastArg = newArgs[newArgs.length - 1];
return function(...args2) {
return curried.apply({ lastArg: newArgs[newArgs.length - 1] },
newArgs.concat(args2));
};
};
}
使用示例
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6
5. 偏函数(Partial)
偏函数是固定函数的部分参数。
function partial(fn, ...args) {
return function(...restArgs) {
return fn.apply(this, args.concat(restArgs));
};
}
// 带占位符的版本
function partialWithPlaceholder(fn, ...args) {
const placeholder = '_';
return function(...restArgs) {
const newArgs = args.map(arg =>
arg === placeholder ? restArgs.shift() : arg
);
return fn.apply(this, newArgs.concat(restArgs));
};
}
6. 组合函数(Compose)
组合多个函数,从右到左依次执行。
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
// 从左到右的版本
function pipe(...fns) {
return function(x) {
return fns.reduce((acc, fn) => fn(acc), x);
};
}
使用示例
const multiply = x => x * 2;
const add = x => x + 1;
const square = x => x * x;
const compute = pipe(add, multiply, square);
console.log(compute(2)); // ((2 + 1) * 2)^2 = 36
7. 惰性求值(Lazy Evaluation)
延迟执行到真正需要的时候。
function lazy(fn) {
let cache = null;
let evaluated = false;
return function(...args) {
if (!evaluated) {
cache = fn.apply(this, args);
evaluated = true;
}
return cache;
};
}
// 通用记忆化
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
8. 深拷贝
function deepClone(obj, hash = new WeakMap()) {
// 处理基本类型和 null
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理日期和正则
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 处理数组和对象
const clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
return clone;
}
9. instanceof 实现
function myInstanceof(left, right) {
// 获取原型
let proto = Object.getPrototypeOf(left);
// 获取构造函数的原型对象
const prototype = right.prototype;
// 遍历原型链
while (proto) {
if (proto === prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
10. new 操作符实现
function myNew(constructor, ...args) {
// 1. 创建新对象
const obj = {};
// 2. 设置原型
Object.setPrototypeOf(obj, constructor.prototype);
// 3. 执行构造函数
const result = constructor.apply(obj, args);
// 4. 返回结果(如果是对象则返回结果,否则返回新对象)
return result instanceof Object ? result : obj;
}
总结
本文介绍了 JavaScript 中常见的 10 种工具函数的实现原理:
- 防抖 - 延迟执行,最后一次触发生效
- 节流 - 固定间隔执行
- Promise - 异步编程解决方案
- 柯里化 - 转换多参数函数
- 偏函数 - 固定部分参数
- 组合函数 - 函数管道
- 惰性求值 - 延迟计算
- 深拷贝 - 完整复制对象
- instanceof - 原型链判断
- new 操作符 - 对象创建过程
掌握这些实现原理,不仅能提升面试竞争力,更能在实际开发中灵活运用,写出更优雅的代码。
参考资料
- MDN Web Docs - Promise
- JavaScript.info
- 《你不知道的 JavaScript》系列