最近学习了一下高阶函数,其中印象比较深刻的有函数消抖,函数节流以及函数柯里化(curry),在学习中也是从一头雾水到逐渐明了,所以写一篇文章对柯里化进行总结。
什么是函数柯里化
javascript忍者中说:在一个函数中首先填充几个参数(然后再返回一个新函数)的技术称为柯里化(Currying)。听起来跟bind的作用是一样的,其实bind也可以采用这种思想来实现(至于bind原本是怎么实现的,我不清楚,控制台输出Function.prototype.bind
,输出是[native code],看不到,不清楚内部原理)。
在很多文章中写到:柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果。
举个不是很恰当的例子,有一个厨师,要做饭,但是餐馆的小儿没有把菜买齐,这样,小儿买一份原料,放在厨师厨房,再买一份,放在厨师厨房,等买齐了,叫厨师过来,好了,原料齐了,可以做饭了。这个时候,厨师利用原料,把饭做好。厨师就像一个函数,他有自己的功能(做饭),但是参数(原料)不齐,每次执行这个函数,在参数不齐的情况下,只能返回一个新的函数,这个新的函数已经内置了之前的参数,当参数齐了之后完成他本身的功能。
问题
要实现一个这样的加法函数,使得:
add(1,2,3)(1)(2)(3)(4,5,6)(7,8)() === 42
首先,可以看到,这个函数,只有当参数为空的时候,才执行之前所有数值的加法,这样的嵌套可以无限进行,当有参数的时候,add(1,2,3),这个时候的返回值应该是一个函数,这个函数存储了1,2,3但是没有执行加法(执行了也行,此处假设就不执行,只是起到保存参数的作用),这样,继续执行add(1,2,3)(2)()
就能输出1+2+3+2=8
。
要实现这样的一个函数,首先,返回值在参数不为空的时候必定返回一个函数,该函数还保存了之前的参数,这就需要用到闭包。
最终的实现如下:
// add 函数柯里化
function add(){
//建立args,利用闭包特性,不断保存arguments
var args = [].slice.call(arguments);
//方法一,新建_add函数实现柯里化
var _add = function(){
if(arguments.length === 0){
//参数为空,对args执行加法
return args.reduce(function(a,b){return a+b});
}else {
//否则,保存参数到args,返回一个函数
[].push.apply(args,arguments);
return _add;
}
}
//返回_add函数
return _add;
// //方法二,使用arguments.callee实现柯里化
// return function () {
// if (arguments.length === 0) {
// return args.reduce(function(a,b){return a+b});
// }
// Array.prototype.push.apply(args, arguments);
// return arguments.callee;
// }
}
console.log(add(1,2,3)(1)(2)(3)(4,5,6)(7,8)());//42
实现的原理主要是:
- 闭包保存args变量,存储之前的参数
- 新建一个_add函数,参数的长度为0,就执行加法,否则,存储参数到args,然后返回函数自身(可以选择匿名函数,返回arguments.callee即可,意思相同,见代码中注释掉的部分,但是在严格模式下不能使用,所以还是使用方法一比较稳妥)。
通用的函数来对普通函数进行柯里化
可以看出来,柯里化其实是有特点的,需要一个闭包保存参数,一个函数来进行递归,这种模式是可以通过一个包装函数,对一些基本的函数进行包装之后的函数具有curry的特性。实现如下:
// 通用的函数柯里化构造方法
function curry(func){
//新建args保存参数,注意,第一个参数应该是要柯里化的函数,所以args里面去掉第一个
var args = [].slice.call(arguments,1);
//新建_func函数作为返回值
var _func = function(){
//参数长度为0,执行func函数,完成该函数的功能
if(arguments.length === 0){
return func.apply(this,args);
}else {
//否则,存储参数到闭包中,返回本函数
[].push.apply(args,arguments);
return _func;
}
}
return _func;
}
function add(){
return [].reduce.call(arguments,function(a,b){return a+b});
}
console.log(curry(add,1,2,3)(1)(2)(3,4,5,5)(5,6,6,7,8,8)(1)(1)(1)());//69
上述代码定义了一个add函数,完成参数相加的功能,通过curry(add),使该函数能够进行柯里化,实现延迟执行。也可以采用arguments.callee来实现,原理相同,只是严格模式下不能使用而已。
事实上,我在看函数节流以及函数消抖的大神写法的时候,其实应该把函数执行的上下文也保存下来,才能更加完善,有待后续更正。
柯里化的作用
看了很多文章,比较常见的功能有以下几种:
- 事件绑定的时候检测应该用dom几的方式,柯里化的话,只需要检测一次,返回一个新的函数来进行事件绑定(我觉得所有需要判断的地方,如果判断条件在一次访问中不改变的话,都可以写成柯里化的形式,从而达到只进行一次判断的效果)
- 利用柯里化的思想,可以自己写一个bind函数
- 延迟执行某个函数,(在一些文章中看到回调函数可以借助这个,还不是很理解,有待之后补充)
小结
这篇文章主要是写了一点自己学习函数柯里化之后的总结,总体来说,学的时候很痛苦,学会了很简单。可能知识就是这么神奇,会者不难。