柯里化,能够理解为提早接收部分参数,延迟执行,不当即输出结果,而是返回一个接受剩余参数的函数。由于这样的特性,也被称为部分计算函数。柯里化,是一个逐步接收参数的过程。在接下来的剖析中,你会深入体会到这一点。javascript
反柯里化,是一个泛型化的过程。它使得被反柯里化的函数,能够接收更多参数。目的是建立一个更普适性的函数,能够被不一样的对象使用。有鸠占鹊巢的效果。java
实现 add(1)(2, 3)(4)() = 10
的效果node
依题意,有两个关键点要注意:缓存
- 传入参数时,代码不执行输出结果,而是先记忆起来
- 当传入空的参数时,表明能够进行真正的运算
完整代码以下:闭包
function currying(fn){ var allArgs = []; return function next(){ var args = [].slice.call(arguments); if(args.length > 0){ allArgs = allArgs.concat(args); return next; }else{ return fn.apply(null, allArgs); } } } var add = currying(function(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return sum; });
因为是延迟计算结果,因此要对参数进行记忆。
这里的实现方式是采用闭包。app
function currying(fn){ var allArgs = []; return function next(){ var args = [].slice.call(arguments); if(args.length > 0){ allArgs = allArgs.concat(args); return next; } } }
当执行var add = currying(...)
时,add
变量已经指向了next
方法。此时,allArgs
在next
方法内部有引用到,因此不能被GC回收。也就是说,allArgs
在该赋值语句执行后,一直存在,造成了闭包。
依靠这个特性,只要把接收的参数,不断放入allArgs
变量进行存储便可。
因此,当arguments.length > 0
时,就能够将接收的新参数,放到allArgs
中。
最后返回next
函数指针,造成链式调用。函数
题意是,空参数时,输出结果。因此,只要判断arguments.length == 0
便可执行。
另外,因为计算结果的方法,是做为参数传入currying
函数,因此要利用apply
进行执行。
综合上述思考,就能够获得如下完整的柯里化函数。优化
function currying(fn){ var allArgs = []; // 用来接收参数 return function next(){ var args = [].slice.call(arguments); // 判断是否执行计算 if(args.length > 0){ allArgs = allArgs.concat(args); // 收集传入的参数,进行缓存 return next; }else{ return fn.apply(null, allArgs); // 符合执行条件,执行计算 } } }
柯里化,在这个例子中能够看出很明显的行为规范:this
实现 add(1)(2, 3)(4)(5) = 15
的效果。
不少人这里就犯嘀咕了:我怎么知道执行的时机?
其实,这里有个忍者技艺:valueOf
和toString
。
js在获取当前变量值的时候,会根据语境,隐式调用valueOf
和toString
方法进行获取须要的值。
那么,实现起来就很简单了。prototype
function currying(fn){ var allArgs = []; function next(){ var args = [].slice.call(arguments); allArgs = allArgs.concat(args); return next; } // 字符类型 next.toString = function(){ return fn.apply(null, allArgs); }; // 数值类型 next.valueOf = function(){ return fn.apply(null, allArgs); } return next; } var add = currying(function(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return sum; });
有如下轻提示类。如今想要单独使用其show
方法,输出新对象obj
中的内容。
// 轻提示 function Toast(option){ this.prompt = ''; } Toast.prototype = { constructor: Toast, // 输出提示 show: function(){ console.log(this.prompt); } }; // 新对象 var obj = { prompt: '新对象' };
用反柯里化的方式,能够这么作
function unCurrying(fn){ return function(){ var args = [].slice.call(arguments); var that = args.shift(); return fn.apply(that, args); } } var objShow = unCurrying(Toast.prototype.show); objShow(obj); // 输出"新对象"
在上面的例子中,Toast.prototype.show
方法,原本是Toast
类的私有方法。跟新对象obj
没有半毛钱关系。
通过反柯里化后,却能够为obj
对象所用。
为何能被obj
所用,是由于内部将Toast.prototype.show
的上下文从新定义为obj
。也就是用apply
改变了this
指向。
而实现这一步骤的过程,就须要增长反柯里化后的objShow
方法参数。
Function.prototype.unCurrying = function(){ var self = this; return function(){ return Function.prototype.call.apply(self, arguments); } } // 使用 var objShow = Toast.prototype.show.unCurrying(); objShow(obj);
这里的难点,在于理解Function.prototype.call.apply(self, arguments);
。
能够分拆为两步:
1) Function.prototype.call.apply(...)
的解析
能够当作是callFunction.apply(...)
。这样,就清晰不少。
callFunction
的this
指针,被apply
修改成self
。
而后执行callFunction
-> callFunction(arguments)
2) callFunction(arguments)
的解析
call
方法,第一个参数,是用来指定this
的。因此callFunction(arguments)
-> callFunction(arguments[0], arguments[1-n])
。
由此能够得出,反柯里化后,第一个参数,是用来指定this
指向的。
3)为何要用apply(self, arguments)
若是使用apply(null, arguments)
,由于null
对象没有call
方法,会报错。
var fn = function(){}; var val = 1; if(Object.prototype.toString.call(fn) == '[object Function]'){ console.log(`${fn} is function.`); } if(Object.prototype.toString.call(val) == '[object Number]'){ console.log(`${val} is number.`); }
上述代码,用反柯里化,能够这么写:
var fn = function(){}; var val = 1; var toString = Object.prototype.toString.unCurrying(); if(toString(fn) == '[object Function]'){ console.log(`${fn} is function.`); } if(toString(val) == '[object Number]'){ console.log(`${val} is number.`); }
function nodeListen(node, eventName){ return function(fn){ node.addEventListener(eventName, function(){ fn.apply(this, Array.prototype.slice.call(arguments)); }, false); } } var bodyClickListen = nodeListen(document.body, 'click'); bodyClickListen(function(){ console.log('first listen'); }); bodyClickListen(function(){ console.log('second listen'); });
使用柯里化,优化监听DOM节点事件。addEventListener
三个参数不用每次都写。
其实,反柯里化和泛型方法同样,只是理念上有一些不一样而已。理解这种思惟便可。