手写的防抖、节流和函数柯里化

7 阅读2分钟

以下是手写的防抖、节流和函数柯里化实现:

1. 防抖 (Debounce)

function debounce(fn, delay, immediate = false) {
  let timer = null;
  let isInvoked = false;

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

    // 清除之前的定时器
    if (timer) clearTimeout(timer);

    // 立即执行
    if (immediate && !isInvoked) {
      fn.apply(context, args);
      isInvoked = true;
    } else {
      timer = setTimeout(() => {
        fn.apply(context, args);
        isInvoked = false;
      }, delay);
    }
  };
}

// 使用示例
const debouncedFn = debounce(() => {
  console.log('执行了');
}, 1000);

2. 节流 (Throttle)

时间戳版本(立即执行)

function throttle(fn, delay) {
  let lastTime = 0;

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

    if (now - lastTime >= delay) {
      fn.apply(context, args);
      lastTime = now;
    }
  };
}

定时器版本(延迟执行)

function throttle(fn, delay) {
  let timer = null;

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

    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(context, args);
        timer = null;
      }, delay);
    }
  };
}

完整版(支持立即执行和延迟执行)

function throttle(fn, delay, immediate = true) {
  let timer = null;
  let lastTime = 0;

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

    if (immediate) {
      // 时间戳版本
      if (now - lastTime >= delay) {
        fn.apply(context, args);
        lastTime = now;
      }
    } else {
      // 定时器版本
      if (!timer) {
        timer = setTimeout(() => {
          fn.apply(context, args);
          timer = null;
        }, delay);
      }
    }
  };
}

3. 函数柯里化 (Currying)

基础版本

function curry(fn) {
  return function curried(...args) {
    // 如果参数够了,直接执行
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    // 否则返回新函数,继续收集参数
    return function(...moreArgs) {
      return curried.apply(this, args.concat(moreArgs));
    };
  };
}

// 使用示例
function sum(a, b, c) {
  return a + b + c;
}

const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6

高级版本(支持占位符)

function curry(fn) {
  return function curried(...args) {
    // 检查是否满足调用条件
    const isEnough = args.length >= fn.length && 
                     !args.slice(0, fn.length).includes(curry.placeholder);
    
    if (isEnough) {
      return fn.apply(this, args);
    }
    
    return function(...moreArgs) {
      const merged = [];
      let moreIndex = 0;
      
      // 合并参数,用新参数替换占位符
      for (let i = 0; i < args.length; i++) {
        if (args[i] === curry.placeholder && moreIndex < moreArgs.length) {
          merged.push(moreArgs[moreIndex++]);
        } else {
          merged.push(args[i]);
        }
      }
      
      // 添加剩余的新参数
      while (moreIndex < moreArgs.length) {
        merged.push(moreArgs[moreIndex++]);
      }
      
      return curried.apply(this, merged);
    };
  };
}

curry.placeholder = Symbol('placeholder');

// 使用示例
const join = (a, b, c) => `${a}_${b}_${c}`;
const curriedJoin = curry(join);
const _ = curry.placeholder;

console.log(curriedJoin(1, _, 3)(2)); // '1_2_3'
console.log(curriedJoin(_, 2)(1, 3)); // '1_2_3'

实用柯里化函数

// 简化版,更常用
const curry = (fn) => {
  const curried = (...args) => {
    if (args.length >= fn.length) {
      return fn(...args);
    }
    return (...next) => curried(...args, ...next);
  };
  return curried;
};

// 实际应用:日志函数
const log = curry((level, time, message) => {
  console.log(`[${level}] ${time}: ${message}`);
});

const errorLog = log('ERROR');
const infoLog = log('INFO');

errorLog('2024-01-01', '出错了'); // [ERROR] 2024-01-01: 出错了

这些实现都是面试中常见的手写题,理解它们的原理和区别很重要:

  • 防抖:频繁触发只执行最后一次
  • 节流:固定时间内只执行一次
  • 柯里化:将多参数函数转换为单参数函数链