【JS】利用闭包实现计数器、once、柯里化

79 阅读3分钟

一、闭包实现计数器

需求:

创建一个 createCounter 函数,每次调用返回一个计数器函数,每次调用计数器函数返回递增的数字。

示例:

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

请你试着补全以下代码:

function createCounter() {
  let count = _______;
  return function () {
    return _______;
  };
}

✅ 正确写法应该是:

function createCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

解释:

  • count 是保存在闭包中的私有变量;
  • 每次调用返回的函数时,count 递增并返回。

二、闭包实现 once 函数

目标:

实现一个函数 once(fn),它接收一个函数 fn,返回一个新函数,只允许 fn 执行一次,后续再调用会直接返回第一次的结果。


示例:

const init = once(() => {
  console.log('初始化中...');
  return 42;
});

console.log(init()); // 输出:初始化中...  → 返回 42
console.log(init()); // 什么都不输出,返回第一次的 42

提示思路:

你需要用闭包保存两样东西:

  1. 是否已经执行过?
  2. 执行过的返回值

请你补全以下代码:

function once(fn) {
  let hasRun = false;
  let result;

  return function () {
    if (!hasRun) {
      result = fn();
      hasRun = true;
    }
    return _______;
  };
}

⏳ 请你填空:返回第一次执行的值

完整代码就是:

function once(fn) {
  let hasRun = false;
  let result;

  return function () {
    if (!hasRun) {
      result = fn();
      hasRun = true;
    }
    return result;
  };
}

很好!现在我们来挑战闭包应用的进阶玩法:


三、利用闭包实现函数柯里化(Currying)

什么是柯里化?

柯里化是指:把一个接收多个参数的函数,转换成一系列每次只接收一个参数的函数。


示例对比:

普通函数:
function add(a, b, c) {
  return a + b + c;
}
console.log(add(1, 2, 3)); // 6
柯里化后:
function curriedAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

console.log(curriedAdd(1)(2)(3)); // 6

每一层返回一个函数,通过闭包“记住”前一个参数。


🧩 小练习:你来写一个柯里化版的乘法函数 multiply(a)(b)(c),返回 a × b × c

请你补全:

function multiply(a) {
  return function (b) {
    return function (c) {
      return _______;
    };
  };
}

⏳ 填空:让最终返回 a * b * c

function multiply(a) {
  return function (b) {
    return function (c) {
      return a * b * c;
    };
  };
}

console.log(multiply(2)(3)(4)); // 24

柯里化 + 闭包原理解析:

  • 每次调用都返回一个新函数,内部通过闭包保留上一次的参数值
  • 最后一层才真正执行运算。

四、「万能柯里化器」:curry(fn),可以把任何固定参数个数的函数转换成柯里化形式

🎯 目标:

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 

思路讲解:

我们要做的是:

  1. 每次调用收集参数;
  2. 如果收集到的参数个数 < fn.length,返回一个新函数继续收;
  3. 如果足够,就调用原函数。

你来补全:万能 curry 函数骨架

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function (...nextArgs) {
        return _________; // 补全这里
      };
    }
  };
}

完整代码

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function (...nextArgs) {
        return curried(...args, ...nextArgs);
      };
    }
  };
}


【总结】你刚刚学到了:

  • 如何用闭包实现参数“记忆”;

  • 如何用递归合并参数直到“收满”为止;

  • 柯里化不仅是面试常客,还在很多库中实际使用(比如 lodash 的 _.curry)。