函数柯里化(currying)
我们假设在查看本文档前,您已经有了一定的js基础,并对高阶函数,闭包,call&&apply有了一定的了解。如果你还不了解这些,那么请你快速去了解这些主题后再回来看这篇文章。
前期要点回顾
* 获取函数声明时的参数数量:func.length
function howMany(a, b, c) {
console.log(howMany.length);
}
howMany(1, 2); // 3
howMany(1, 2, 3, 4); // 3
* 获取函数调用时的所有参数:arguments
Javascript 允许我们通过函数作用域内可用的 arguments 变量来访问传递给函数的所有参数。arguments 变量是一个包含函数调用时传递给该函数的所有参数的 类数组(array-like) 列表。
将类数组转化为数组
function showArgs() {
let args = [].slice.call(arguments); //转化为数组
}
等同于
function showArgs() {
let args =Array.prototype.slice.call(arguments); //转化为数组
}
在ES6中,我利用 spread operator(扩展操作符) / rest parameters(剩余参数) 更容易操作函数的参数
function howMany(...args) {
console.log("args:", args, ", length:", args.length);
}
howMany(1,2,3,4); // args: [1,2,3,4], length: 4 (a "real" array)!
那么我们可以继续我们的话题了!
函数柯里化(currying)
函数柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
eg:
(1)我们有一个 add() 函数接受 3 个参数并返回总和
function add(a,b,c) { return a+b+c; }
(2)我们可以把它转换成一个 Currying(柯里化) 函数,如下:
function curriedAdd(a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
Currying(柯里化) 是如何工作?它的工作方式是通过为每个可能的参数嵌套函数,使用由嵌套函数创建的自然闭包来保留对每个连续参数的访问。
但是我们想要的是一种轻松地办法,可以将现有的带 N 个参数的函数转换成它的 Currying(柯里化) 版本,而不用像 curriedAdd() 那样写出每个函数 的 Currying(柯里化) 版本。
编写一个通用的 curry()
这是我们想要设计的 curry() 函数接口
function add(a,b,c){
return a+b+c;
}
var curriedAdd = curry(add);
curriedAdd(1,2,3); // 6
curriedAdd(1)(2,3); // 6
curriedAdd(1)(2)(3); // 6
curriedAdd(1,2)(3); // 6
我们的 curry() 返回一个新的函数,允许我们用一个或多个参数来调用它,然后它将部分应用;直到它收到最后一个参数(基于原始函数的参数数量),此时它将返回使用所有参数调用原始函数的计算值。
而且,我们还需要存储传递的原始函数,所以一旦我们有了所有必需的参数,我们可以使用正确的参数调用原始函数并返回其结果。
function curry(fn) {
return function curried() {
var args = [].slice.call(arguments);
return args.length >= fn.length ? fn.apply(null, args) :
function () {
var rest = [].slice.call(arguments);
return curried.apply(null, args.concat(rest));
};
};
}让我们详细解释一下
第 2 行:我们的 curry 函数返回一个新的函数,在这个例子中是一个名为 curried() 的命名函数表达式。
第 3 行:每次此函数被调用时,我们在 args 中存储传递给它的参数;
第 4 行:如果参数的数量大于等于原始函数的数量,那么
第 5 行:返回使用所有参数调用的原始函数
第 6 行:否则,返回一个接受更多参数的函数,当被调用时,将使用之前传递的原始参数与传递给新返回的函数的参数结合在一起,再次调用我们的 curried 函数。
我们来试一下我们原来的 add 函数。
var curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1)(2,3); // 6
似乎得到了我们想要的结果。
但是,我们必须保留原始的上下文,并确保并将其传递给已返回的 curried 函数的连续调用。
function curry(fn) {
return function curried() {
var args = toArray(arguments),
context = this;
return args.length >= fn.length ? fn.apply(context, args) :
function () {
var rest = toArray(arguments);
return curried.apply(context, args.concat(rest));
};
}
}
现在我们的 curry() 函数可以正确感知上下文了,并且可以在任何情况下用作函数装饰器了。