JavaScript柯里化的实现

背景 😎

看到柯里化的实现觉得不好理解,所以做了分析。

本篇简单介绍柯里化的定义,主要介绍柯里化的实现的分析。

定义

柯里化是将多个参数的函数分解成多个一个参数进行调用的方式。

js 复制代码
curry(function (a, b, c) {return [a, b, c]})("a")("b")("c")

柯里化的"高级"实现还可以有各种传参方法:

js 复制代码
let curfn = curry(function (a, b, c) {return [a, b, c]});
curfn("a", "b", "c") // ["a", "b", "c"]
curfn("a", "b")("c") // ["a", "b", "c"]
curfn("a")("b")("c") // ["a", "b", "c"]
curfn("a")("b", "c") // ["a", "b", "c"]

柯里化的实现

普通实现:

js 复制代码
function curry(fn) {
  let args = Array.prototype.slice.call(arguments, 1)
  return function() {
    let newargs = args.concat(Array.prototype.slice.apply(arguments));
    return fn.apply(this, newargs);
  }
}

普通实现的问题是只能分2次调用curry()()

js 复制代码
✅let name = curry(function (key, obj) {return obj[key]})('name',{name: 'jack'});
console.log(name);

不能分1次、3次或更多次curry()()()

js 复制代码
❌let name1 = curry(function (key, obj) {return obj[key]})('name')({name: 'mark'})
console.log(name1);

因为curry已经返回一个回调函数了,到('name')已经执行掉这个回调,没有其他回调了,再次({name:'mark'})就报错。

递归实现:适应任意层次的调用

js 复制代码
function sub_curry(fn) {
  var args = [].slice.call(arguments, 1);
  return function() {
      return fn.apply(this, args.concat([].slice.call(arguments)));
  };
}

function curry(fn, length) {
  length = length || fn.length;
  var slice = Array.prototype.slice;
  return function() {
      if (arguments.length < length) {
          var combined = [fn].concat(slice.call(arguments));
          //sub_curry.apply(this, combined)把外层的参数拼接到fn,curry(fn),curry(fn,a),curry(fn,a,b),curry(fn,a,b,c)
          return curry(sub_curry.apply(this, combined), length - arguments.length);
      } else {
          return fn.apply(this, arguments);
      }
  };
}

实现分析

注:图中左右两边是等价的

最内层的fn是什么?最后一次将执行return fn.apply(this,arguments),包括sub_curry内也将执行return fn.apply(this, args.concat([].slice.call(arguments))),这些fn是什么?

js 复制代码
curry(function (key, obj) { return obj[key] })('name')(obj)

js是静态作用域,因此,fn在curry声明的时候就确定是function curry(fn)的形参fn。 沿着作用域链向上找,...sub_curry.apply(this,combined2)2------>sub_curry.apply(this,combined1)1------>function (key, obj) {return obj[key]}

sub_curry执行也返回一个函数function() {return fn.apply(this,args.concat([].slice.call(arguments)));};

combined是[fn,arguments]

js 复制代码
 combined1所在的作用域的fn是
function (key, obj) { return obj[key] }
 所以sub_curry.apply(this, combined1)1是
function () {return (function (key, obj) { return obj[key] }).apply(this, args.concat([].slice.call(arguments)));};
 combined2所在的作用域的fn是
function () {return ([sub_curry.apply(this, combined1)1]).apply(this, args.concat([].slice.call(arguments)));};
 所以sub_curry.apply(this, combined2)2是
function () {return (function () {return ([sub_curry.apply(this, combined1)1]).apply(this, args.concat([].slice.call(arguments)));};).apply(this, args.concat([].slice.call(arguments)));};

其他实现

这里还有更好理解的写法,原理是相同的,做函数参数的拼接

js 复制代码
function curry(fn, length) {
  length = length || fn.length;
  return function() {
    if(arguments.length < length) {
      let args = [].slice.call(arguments);
      let cb = function() {//同样的原理换一种写法,把外层的参数拼接到fn,curry(fn),curry(fn,a),curry(fn,a,b),curry(fn,a,b,c)
        return fn.apply(this, args.concat([...arguments]));
      }
      return curry(cb, length - arguments.length);
    } else {
      return fn.apply(this, arguments)
    }
  }
}
相关推荐
必须会一定会2 小时前
用纯 HTML/JS 做一个 AI 需求澄清器:把模糊想法转换成可执行任务书
开发语言·前端·javascript·人工智能·html·ai编程
小帅不太帅2 小时前
1.5M 参数的 OCR 模型,我把它跑进了浏览器
前端·javascript·ai编程
avi911114 小时前
[AI教做人]AI平台做2项目;一个3D模型展示,另一个框架多人
javascript·人工智能·ai·3d模型·3d引擎·顶点和法线
kyriewen15 小时前
我用Claude Code两天干完了团队两周的排期——周报发出去那一刻我就后悔了
前端·javascript·ai编程
用户9385156350715 小时前
手写一个 LLM Harness 框架:用工程化手段把大模型幻觉踩在脚下
javascript·人工智能·后端
油丶酸萝卜别吃16 小时前
jquery-ajax.js 说明文档
前端·javascript·jquery
windliang16 小时前
Claude Code 源码分析(九):子 Agent 如何分叉、继续与回到父会话
前端·javascript·面试
柒和远方17 小时前
V063: TS 面试必考:interface 与 type 的四大差异,与 LLM Harness 的自动化择优
前端·javascript
汉堡大王952717 小时前
Vue 3 + TS + Element Plus 实战:如何从零搭建企业级违章记录管理 SaaS 前端
前端·javascript·vue.js
海天鹰17 小时前
屏幕颜色检测
javascript·html5