著有《React 设计原理》《React 用到的一些算法》《javascript地月星》等多个专栏。欢迎关注。
欢迎收看我的专栏,文章不好写,要是有帮助别忘了点赞👍、收藏⭐️、评论📝,你的鼓励是我继续挖干货的动力🔥🔥。
另外,本文为原创内容,商业转载请联系作者获得授权,非商业转载需注明出处,感谢理解~
大家好,我是骑自行车,开奔驰,住别墅的码农。本文简单介绍柯里化的定义,从「静态作用域-参数」和「调用时机」的角度分析柯里化。
定义
柯里化是将多个参数的函数分解成多个一个参数进行调用的方式。
curry(function (a, b, c) {return [a, b, c]})("a")("b")("c")
柯里化的“高级”实现还可以有各种传参方法:
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"]
柯里化的实现
- 普通实现:
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()():
✅let name = curry(function (key, obj) {return obj[key]})('name',{name: 'jack'});
console.log(name);
不能分1次、3次或更多次curry()()()
❌let name1 = curry(function (key, obj) {return obj[key]})('name')({name: 'mark'})
console.log(name1);
因为curry已经返回一个回调函数了,到('name')已经执行掉这个回调,没有其他回调了,再次({name:'mark'})就报错。
- 递归实现:适应任意层次的调用
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);
}
};
}
length初始值是fn.length, 然后return curry(cb, length-arguments.length)更新为剩下的参数个数。
例如:curry(fn(a,b,c))('a')('b','c'),fn.length是3,lenght初始值是3。
curry(fn(a,b,c)('a')后length更新为2。
curry(fn(a,b,c))('a')('b','c')时argument.length=2,length=2,
不会执行“curry(cb, length - arguments.lenght)更新length,减2变成0”,而是结束递归,执行fn.apply(this,arguments)。
curry
curry(fn)返回的是return function(){if(arguments.length<length){} else {})。
curry(fn)(参数1)执行这个匿名函数,返回return curry(sub_curry.apply(this,[fn,参数1]),)。
curry和sub_curry的fn
最内层的fn是什么?
js是静态作用域,因此,fn在curry声明的时候就确定是function curry(fn,length){}的形参fn。
沿着作用域链向上找(从下往上),
fn红色——>
...蓝色——>
sub3 = sub_curry.apply(this,combined)紫色——>
sub2= sub_curry.apply(this,combined)绿色——>
sub1 = sub_curry.apply(this,combined)黄色——>
原函数例如function (key, obj) { return obj[key] }
为了简化,省略蓝色:
原函数组装3次,就是红色的fn
sub1 = sub_curry.apply(this,[原函数])
sub2 = sub_curry.apply(this,[sub1])
sub3 = sub_curry.apply(this,[sub2])
sub1 = function(){
return 原函数.apply(this,args.concat([].slice.arguments)))
}
sub2 = function(){
return sub1.apply(this,args.concat([].slice.arguments)))
}
sub3 = function(){
return sub2.apply(this,args.concat([].slice.arguments)))
}
sub2 = function(){
return function(){
return 原函数.apply(this,args.concat([].slice.arguments)))
}.apply(this,args.concat([].slice.arguments)))
}
sub3 = function(){
return function(){
return function(){
return 原函数.apply(this,args.concat([].slice.arguments)))
}.apply(this,args.concat([].slice.arguments)))
}.apply(this,args.concat([].slice.arguments)))
}
结论:最内层的fn就是sub3。sub3一旦执行就能一层层拼接参数。
大概可能也许是这样...
结论
递归长长的一段源码,例如科里化,最终组成的就是一段调用方式的拼接,如上的短短的一段调用的拼接。
function curry{}()()() === function(){ return function(){}.apply(this,)}.apply(this,)
原函数最后才执行,一层递归拼接一部分参数,执行到原函数时参数全部拼接好了,就可以执行原函数了。
执行函数——>返回匿名函数,称为柯里化也好,称为拼接模式也好,把原函数放到返回的匿名函数中,具有「延迟执行」原函数的作用,匿名函数执行时还能利用arguments把它的参数拼接到原函数。所以它的本质是,利用这种模式,不断的把参数拼接到原函数中。即:
fn1=function(){ return 原函数.apply() } 把参数拼接到原函数
fn2=function(){ return fn1.apply() } 把参数拼接到函数fn1
...
fn2把参数拼接给fn1,fn1把全部参数拼接给原函数。
柯里化的本质是执行函数——>返回匿名函数实现的参数拼接算法。
这里还有更好理解的写法,原理是相同的,做函数参数的拼接
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)
}
}
}