背景 😎
看到柯里化的实现觉得不好理解,所以做了分析。
本篇简单介绍柯里化的定义,主要介绍柯里化的实现的分析。
定义
柯里化是将多个参数的函数分解成多个一个参数进行调用的方式。
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)
}
}
}