6 min read

JavaScript 原生方法与工具函数实现指南

Table of Contents

前言

日常开发中,我们经常会用到各种工具函数,比如防抖、节流、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 种工具函数的实现原理:

  1. 防抖 - 延迟执行,最后一次触发生效
  2. 节流 - 固定间隔执行
  3. Promise - 异步编程解决方案
  4. 柯里化 - 转换多参数函数
  5. 偏函数 - 固定部分参数
  6. 组合函数 - 函数管道
  7. 惰性求值 - 延迟计算
  8. 深拷贝 - 完整复制对象
  9. instanceof - 原型链判断
  10. new 操作符 - 对象创建过程

掌握这些实现原理,不仅能提升面试竞争力,更能在实际开发中灵活运用,写出更优雅的代码。


参考资料