在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
柯里化有3个常见作用:
function say2(company){
return function(academy){
return function(name){
console.log(`我的公司是${company},专业是${academy},名字是${name}`);
}
}
}
let setInfo = say2('蜀国')('武将'); // 提取公用参数,避免每次调用传递相同的参数(如下处注释)
// say('蜀国','武将','张飞')
// say('蜀国','武将','关羽')
// say('蜀国','武将','赵云')
setInfo('张飞')
setInfo('关羽')
setInfo('赵云')
// 按照分步求值,我们看一个简单的例子
var concat3Words = function (a, b, c) {
return a+b+c;
};
var concat3WordsCurrying = function(a) {
return function (b) {
return function (c) {
return a+b+c;
};
};
};
console.log(concat3Words("foo ","bar ","baza")); // foo bar baza
console.log(concat3WordsCurrying("foo ")); // [Function]
console.log(concat3WordsCurrying("foo ")("bar ")("baza")); // foo bar baza
可以看到, concat3WordsCurrying("foo ") 是一个 Function,每次调用都返回一个新的函数,该函数接受另一个调用,然后又返回一个新的函数,直至最后返回结果,分布求解,层层递进。(PS:这里利用了闭包的特点)
那么现在我们更进一步,如果要求可传递的参数不止3个,可以传任意多个参数,当不传参数时输出结果?
首先来个普通的实现:
var add = function(items){
return items.reduce(function(a,b){
return a+b
});
};
console.log(add([1,2,3,4]));
但如果要求把每个数乘以10之后再相加,那么:
var add = function (items,multi) {
return items.map(function (item) {
return item*multi;
}).reduce(function (a, b) {
return a + b
});
};
console.log(add([1, 2, 3, 4],10));
好在有 map 和 reduce 函数,假如按照这个模式,现在要把每项加1,再汇总,那么我们需要更换map中的函数。
下面看一下柯里化实现:
var adder = function () {
var _args = [];
return function () {
if (arguments.length === 0) {
return _args.reduce(function (a, b) {
return a + b;
});
}
[].push.apply(_args, [].slice.call(arguments));
return arguments.callee;
}
};
var sum = adder();
console.log(sum); // Function
sum(100,200)(300); // 调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用
sum(400);
console.log(sum()); // 1000 (加总计算)
等等~~上述代码有问题,原因:
访问 arguments 是个很昂贵的操作,因为它是个很大的对象,每次递归调用时都需要重新创建。影响现代浏览器的性能,还会影响闭包。
现在arguments.callee 被弃用了。怎么办,其实很简单,给内部函数一个名字即可(当函数被调用时,它的arguments.callee对象就会指向自身,也就是一个对自己的引用。)
var adder = function () {
var _args = [];
return function fn() {
if (arguments.length === 0) {
return _args.reduce(function (a, b) {
return a + b;
});
}
[].push.apply(_args, [].slice.call(arguments));
return fn;
}
};
var sum = adder();
console.log(sum); // Function
sum(100,200)(300); // 调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用
sum(400);
console.log(sum()); // 1000 (加总计算)
上面 adder是柯里化了的函数,它返回一个新的函数,新的函数接收可分批次接受新的参数,延迟到最后一次计算。
通用的柯里化函数
更典型的柯里化会把最后一次的计算封装进一个函数中,再把这个函数作为参数传入柯里化函数,这样即清晰,又灵活。
例如 每项乘以10, 我们可以把处理函数作为参数传入:
var currying = function (fn) {
var _args = [];
return function fn() {
if (arguments.length === 0) {
return fn.apply(this, _args);
}
Array.prototype.push.apply(_args, [].slice.call(arguments));
return fn;
}
};
var multi=function () {
var total = 0;
for (var i = 0, c; c = arguments[i++];) {
total += c;
}
return total;
};
var sum = currying(multi);
sum(100,200)(300);
sum(400);
console.log(sum()); // 1000 (空白调用时才真正计算)
这样 sum = currying(multi),调用非常清晰,使用效果也堪称绚丽,例如要累加多个值,可以把多个值作为做个参数 sum(1,2,3),也可以支持链式的调用,sum(1)(2)(3)
// 封装
function curry(fn){
let len = fn.length;
return function temp(){
let args = [...arguments];
console.log(...args,arguments,args);
if(args.length>=len){
console.log(11);
return fn(...args);
}else{
return function(){
console.log(22);
return temp(...args,...arguments)
}
}
}
}
function isType(type){
return function(obj){
return Object.prototype.toString.call(obj).includes(type);
}
}
// 包装成一个高阶函数,批量生成函数
let types = ['String','Object','Array','Null','Undefined','Boolean'];
let fns=Object.create(null)
types.forEach(type=>{ // 批量生成方法
fns['is'+type] = isType(type)
})
let boolean = null;
console.log(fns.isNull(boolean)); // 函数柯里化
console.log(fns.isString(boolean));
console.log(fns.isObject(boolean));
console.log(fns.isArray(boolean));