一个小小的柯里化函数

看段简单的代码:

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)。
相关推荐
耶啵奶膘2 小时前
uniapp+firstUI——上传视频组件fui-upload-video
前端·javascript·uni-app
视频砖家2 小时前
移动端Html5播放器按钮变小的问题解决方法
前端·javascript·viewport功能
lyj1689973 小时前
vue-i18n+vscode+vue 多语言使用
前端·vue.js·vscode
小白变怪兽4 小时前
一、react18+项目初始化(vite)
前端·react.js
ai小鬼头4 小时前
AIStarter如何快速部署Stable Diffusion?**新手也能轻松上手的AI绘图
前端·后端·github
墨菲安全5 小时前
NPM组件 betsson 等窃取主机敏感信息
前端·npm·node.js·软件供应链安全·主机信息窃取·npm组件投毒
GISer_Jing5 小时前
Monorepo+Pnpm+Turborepo
前端·javascript·ecmascript
天涯学馆5 小时前
前端开发也能用 WebAssembly?这些场景超实用!
前端·javascript·面试
我在北京coding6 小时前
TypeError: Cannot read properties of undefined (reading ‘queryComponents‘)
前端·javascript·vue.js
前端开发与ui设计的老司机7 小时前
UI前端与数字孪生结合实践探索:智慧物流的货物追踪与配送优化
前端·ui