引子
有如许一道题目,完成一个函数,完成以下功用:
var result = sum(1)(2)(3);
console.log(result);//6
这道题目,印象中是一道手艺笔试题。连系查到的材料,在这里做一下简朴的剖析和总结。
一个简朴的例子
题目给的照样比较宽的,没多少限定,给了许多自由发挥的空间。
下面我们就一步一步的去完成,一种简朴的做法可所以如许的:
function add(a){
var sum = 0;
sum += a;
return function(b){
sum += b;
return function(c){
sum += c;
return sum;
}
}
}
add(1)(2)(3);//6
嗯,没什么题目。
在此基础上,我们再进一步:
假如对挪用的次数不加限定,比方 四次,那上面的代码就不行了。
那该怎么办呢?
视察一下,我们可以发明返回的每个函数实行的逻辑实在都是一样的。
就此我们可以精简下代码,让函数返回后返回自身。
来试一下:
function add(a){
var sum = 0;
sum += a;
return function temp(b) {
sum += b;
return temp;
}
}
add(2)(3)(4)(5);
输出的效果:
//function temp(b) {
// sum += b;
// return temp;
// }
并没有像我们预期的那样输出 14,实际上是如许的,每次函数挪用后返回的就是一个函数对象,那末了的效果,肯定是一个字符串示意啊。
要修改的话,有两个要领。
- 推断参数,当没有输入参数时,返回挪用效果:
function add(a){
var sum = 0;
sum += a;
return function temp(b) {
if (arguments.length === 0) {
return sum;
} else {
sum= sum+ b;
return temp;
}
}
}
add(2)(3)(4)(5)(); //14
假如要运用匿名函数,也可以:
function add() {
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 = add();
sum(2,3)(4)(5)(); //14
2 . 运用JS中对象到原始值的转换规则。
当一个对象转换成原始值时,先检察对象是不是有valueOf要领。
假如有而且返回值是一个原始值,那末直接返回这个值。
假如没有valueOf 或 返回的不是原始值,那末挪用toString要领,返回字符串示意。
那我们就为函数对象增加一个valueOf要领 和 toString要领:
function add(a) {
var sum = 0;
sum += a;
var temp = function(b) {
if(arguments.length===0){
return sum;
} else {
sum = sum+ b;
return temp;
}
}
temp.toString = temp.valueOf = function() {
return sum;
}
return temp;
}
add(2)(3)(4)(5); //14
写到这里,我们来简朴总结下。
柯里化的定义
柯里化一般也称部份求值,其寄义是给函数分步通报参数,每次通报参数后,部份运用参数,并返回一个更详细的函数接收剩下的参数,中心可嵌套多层如许的接收部份参数函数,逐渐减少函数的适用范围,逐渐求解,直至返回末了效果。
一个通用的柯里化函数
var curring = function(fn){
var _args = [];
return function cb(){
if(arguments.length === 0) {
return fn.apply(this, _args);
}
Array.prototype.push.apply(_args, [].slice.call(arguments));
return cb;
}
}
var multi = function(){
var total = 0;
var argsArray = Array.prototype.slice.call(arguments);
argsArray.forEach(function(item){
total += item;
})
return total
};
var calc = curring(multi);
calc(1,2)(3)(4,5,6);
console.log(calc()); //空缺挪用时才真正盘算
如许 calc = currying(multi),挪用异常清楚.
假如要 累加多个值,可以把多个值作为做个参数 calc(1,2,3),也可以支撑链式的挪用,如 calc(1)(2)(3);
到这里, 不难看出,柯里化函数具有以下特征:
- 函数可以作为参数通报
- 函数可以作为函数的返回值
- 闭包
说了这么多,柯里化函数究竟可以帮我做什么,或者说,我为何要用柯里化函数呢? 我们接着往下看。
柯里化函数的作用
函数柯里化许可和勉励你分开庞杂功用变成更小更轻易剖析的部份。这些小的逻辑单位显著是更轻易明白和测试的,然后你的运用就会变成清洁而整齐的组合,由一些小单位构成的组合。
1.进步通用性
function square(i) {
return i * i;
}
function double(i) {
return i *= 2;
}
function map(handeler, list) {
return list.map(handeler);
}
// 数组的每一项平方
map(square, [1, 2, 3, 4, 5]);
map(square, [6, 7, 8, 9, 10]);
map(square, [10, 20, 30, 40, 50]);
// ......
// 数组的每一项更加
map(double, [1, 2, 3, 4, 5]);
map(double, [6, 7, 8, 9, 10]);
map(double, [10, 20, 30, 40, 50]);
例子中,建立了一个map通用函数,用于顺应差别的运用场景。显著,通用性不必疑心。同时,例子中反复传入了雷同的处置惩罚函数:square和dubble。
运用中这类可能会更多。固然,通用性的加强必定带来适用性的削弱。然则,我们依旧可以在中心找到一种均衡。
看下面,我们运用柯里化革新一下:
function currying(fn) {
var slice = Array.prototype.slice,
__args = slice.call(arguments, 1);
return function () {
var __inargs = slice.call(arguments);
return fn.apply(null, __args.concat(__inargs));
};
}
function square(i) {
return i * i;
}
function double(i) {
return i *= 2;
}
function map(handeler, list) {
return list.map(handeler);
}
var mapSQ = currying(map, square);
mapSQ([1, 2, 3, 4, 5]); //[1, 4, 9, 16, 25]
var mapDB = currying(map, double);
mapDB([1, 2, 3, 4, 5]); //[2, 4, 6, 8, 10]
我们减少了函数的适用范围,但同时进步函数的适性.
2 耽误实行。
柯里化的另一个运用场景是耽误实行。不停的柯里化,积累传入的参数,末了实行。
3.牢固易变要素。
柯里化特征决议了它这运用场景。提早把易变要素,传参牢固下来,天生一个更明白的运用函数。最典范的代表运用,是bind函数用以牢固this这个易变对象。
Function.prototype.bind = function(ctx) {
var fn = this;
return function() {
fn.apply(ctx, arguments);
};
};
Function.prototype.bind 要领也是柯里化运用与 call/apply 要领直接实行差别,bind 要领 将第一个参数设置为函数实行的上下文,其他参数顺次通报给挪用要领(函数的主体自身不实行,可以看成是耽误实行),并动态建立返回一个新的函数, 这相符柯里化特征。
var foo = {
x: 666
};
var bar = function () {
console.log(this.x);
}.bind(foo); // 绑定
bar(); //666
// 下面是一个 bind 函数的模仿,testBind 建立并返回新的函数,在新的函数中将真正要实行营业的函数绑定到实参传入的上下文,耽误实行了。
Function.prototype.testBind = function (scope) {
var self = this; // this 指向的是挪用 testBind 要领的一个函数,
return function () {
return self.apply(scope);
}
};
var testBindBar = bar.testBind(foo); // 绑定 foo,耽误实行
console.log(testBindBar); // Function (可见,bind以后返回的是一个耽误实行的新函数)
testBindBar(); // 666
关于curry机能的备注
一般,运用柯里化会有一些开支。取决于你正在做的是什么,可能会或不会,以显著的体式格局影响你。也就是说,险些大多数状况,你的代码的具有机能瓶颈起首来自其他缘由,而不是这个。
有关机能,这里有一些事变必需牢记于心:
- 存取arguments对象一般要比存取定名参数要慢一点.
- 一些老版本的浏览器在arguments.length的完成上是相称慢的.
- 建立大批嵌套作用域和闭包函数会带来花消,无论是在内存照样速度上.
以上 ;)
参考材料
http://blog.jobbole.com/77956/
http://www.cnblogs.com/pigtai…