一个小小的柯里化函数

看段简单的代码:

JS 复制代码
function curry(fn) {
  // curry 函数接收一个原始函数 fn 作为参数
  return function curried(...args) {
    // curried 是被 curry 函数返回的"柯里化"后的函数。
    // 每次调用 curried 时,它都会收集当前传入的参数 args。

    // 1. 判断是否收集到足够的参数
    // fn.length 是原始函数 fn 期望的参数个数(即函数的"元数"或"arity")。
    // args.length 是当前已经收集到的参数个数。
    if (args.length >= fn.length) {
      // 如果当前收集到的参数数量 (args.length) 大于或等于
      // 原始函数 fn 期望的参数数量 (fn.length),
      // 说明我们已经收集到了足够的参数来执行原始函数 fn。

      // 使用 apply 方法调用原始函数 fn。
      // apply 的第一个参数是 this 的上下文,这里我们保持调用时的 this。
      // 第二个参数是一个数组,包含了所有收集到的参数 args。
      return fn.apply(this, args);
    } else {
      // 2. 参数不足,返回一个新的函数继续收集参数
      // 如果当前收集到的参数数量不足以执行原始函数 fn,
      // 那么 curried 函数会返回一个新的函数。
      return function(...args2) {
        // 这个新函数接收额外的参数 args2。
        // 它会递归地调用 curried 函数本身。

        // 使用 args.concat(args2) 将之前收集的参数 (args)
        // 和当前新传入的参数 (args2) 合并成一个新的参数数组。
        // 然后,将这个合并后的参数数组传递给 curried 函数进行下一次判断。
        // 同样,使用 apply 保持 this 上下文。
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}

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

const curriedSum = curry(sum);

逐步解析 curriedSum 的调用过程:

假设 sum 函数的 fn.length3

  1. const curriedSum = curry(sum);

    • curry(sum) 被调用。
    • 它返回一个名为 curried 的内部函数。curriedSum 现在就是这个 curried 函数。
    • 此时,curried 函数内部的闭包记住了 fn 就是 sum
  2. curriedSum(1)

    • curried 函数被调用,args 是 ``。
    • args.length (1) < fn.length (3),条件 if (args.length >= fn.length) 不满足。
    • 进入 else 分支。
    • 返回一个新的匿名函数。这个匿名函数内部的闭包记住了当前的 args 是 ``。
    • 所以 curriedSum(1) 的结果是一个新的函数。
  3. curriedSum(1)(2)

    • 上一步返回的匿名函数被调用,args2 是 ``。
    • 这个匿名函数执行 curried.apply(this, args.concat(args2))
    • args (来自上一个闭包) 是 ,`args2` 是
    • args.concat(args2) 结果是 ``。
    • 现在 curried 函数被递归调用,传入的 args 是 ``。
    • 在这次 curried 调用中,args.length (2) < fn.length (3),条件不满足。
    • 再次进入 else 分支。
    • 返回一个新的匿名函数。这个匿名函数内部的闭包记住了当前的 args 是 ``。
    • 所以 curriedSum(1)(2) 的结果又是一个新的函数。
  4. curriedSum(1)(2)(3)

    • 上一步返回的匿名函数被调用,args2 是 ``。
    • 这个匿名函数执行 curried.apply(this, args.concat(args2))
    • args (来自上一个闭包) 是 ,`args2` 是
    • args.concat(args2) 结果是 ``。
    • 现在 curried 函数被递归调用,传入的 args 是 ``。
    • 在这次 curried 调用中,args.length (3) >= fn.length (3),条件满足!
    • 进入 if 分支。
    • 执行 fn.apply(this, args),即 sum.apply(this,)
    • sum(1, 2, 3) 返回 6
    • 所以 curriedSum(1)(2)(3) 的结果是 6

这种柯里化函数的特点和优点:

  • 灵活性 :它不仅支持链式调用(每次一个参数),也支持一次性提供所有参数,或者分批提供参数,例如 curriedSum(1, 2, 3)curriedSum(1, 2)(3)

  • 参数复用(Partial Application) :你可以创建一个预先填充了部分参数的新函数。例如:

    js 复制代码
    const addOneAndTwo = curriedSum(1, 2); // addOneAndTwo 现在是一个等待第三个参数的函数
    console.log(addOneAndTwo(3)); // 输出 6 (1 + 2 + 3)
    console.log(addOneAndTwo(10)); // 输出 13 (1 + 2 + 10)
  • 延迟执行 :原始函数 fn 不会立即执行,直到所有必需的参数都已提供。

  • 函数组合:柯里化函数在函数式编程中非常有用,因为它使得函数的组合变得更加容易和自然。

涉及的关键概念:

  • 闭包(Closure)curried 函数以及它内部返回的匿名函数都形成了闭包。它们记住了自己被创建时的环境(例如 fn,以及每次调用时累积的 args),即使外部函数已经执行完毕,这些内部函数仍然可以访问和操作这些变量。
  • fn.length:这是JavaScript函数的一个内置属性,表示函数声明时期望的参数个数。
  • apply() :用于调用函数,并允许你以数组的形式传递参数,同时可以指定 this 的上下文。
  • 递归(Recursion)curried 函数在 else 分支中递归地调用了自身,这是实现参数累积和链式调用的关键。
  • this 上下文 :代码中使用了 this,这意味着柯里化后的函数在调用时会保留其 this 上下文。在严格模式下,如果函数是作为独立函数调用,this 会是 undefined;如果作为对象方法调用,this 会指向该对象。在非严格模式下,独立函数调用时 this 会指向全局对象(浏览器中的 window)。
相关推荐
coding随想4 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
灵感__idea4 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
小小小小宇4 小时前
前端双Token机制无感刷新
前端
小小小小宇5 小时前
重提React闭包陷阱
前端
小小小小宇5 小时前
前端XSS和CSRF以及CSP
前端
UFIT5 小时前
NoSQL之redis哨兵
java·前端·算法
超级土豆粉5 小时前
CSS3 的特性
前端·css·css3
星辰引路-Lefan5 小时前
深入理解React Hooks的原理与实践
前端·javascript·react.js
wyn200011285 小时前
JavaWeb的一些基础技术
前端