前端JS柯里化函数
一、柯里化和柯南的关系是?
回答:如果我说“柯里化 == 柯南”呢?
众人:博主,r u ok!? 是不是钓鱼钓久了脑袋秀逗了哈?柯里化可是函数式编程中的一个技巧,而柯南是到哪儿哪儿死人、10年不老的神话般的存在。八竿子都打不到的,怎会相等呢??
回答:诸位,眼睛睁大点,是==
, 不是===
哦~
众人:嗯哪,我眼睛已经瞪得灯泡大了,粑粑并没有变冰淇淋啊?
回答:这不就结了嘛。我说的是弱等于==
, 又不是强等于===
. 你想那,JS的世界里,0==false
. 你看,这阿拉伯届的数字0
,和英美届的单词false
不就相等了,这两个家伙也是八竿子都达不到的哦。
众人:这……好吧,村妇吵架道理多,恕我们愚钝,还是看不出来“柯里化==柯南”,喔~除了那个“柯”字是一样的,求解释~~
回答:百科中柯里化解释为:“@#¥#¥#%*&……%@#¥①”,太术语了,我镜片看裂才知大概。若非要套用定义(见下面的①标注的引用),个人觉得:“柯里化”就像某些官员的把戏,官员要弄7个老婆,碍于国策(一夫一妻)以及年老弟衰,表面上就1个老婆,实际上剩下的6个暗地里消化。代码表示就是:
var currying = function(fn) { // fn 指官员消化老婆的手段 var args = [].slice.call(arguments, 1); // args 指的是那个合法老婆 return function() { // 已经有的老婆和新搞定的老婆们合成一体,方便控制 var newArgs = args.concat([].slice.call(arguments)); // 这些老婆们用 fn 这个手段消化利用,完成韦小宝前辈的壮举并返回 return fn.apply(null, newArgs); }; }; // 下为官员如何搞定7个老婆的测试 // 获得合法老婆 var getWife = currying(function() { var allWife = [].slice.call(arguments); // allwife 就是所有的老婆的,包括暗渡陈仓进来的老婆 console.log(allWife.join(";")); }, "合法老婆"); // 获得其他6个老婆 getWife("大老婆","小老婆","俏老婆","***蛮老婆","乖老婆","送上门老婆"); // 换一批老婆 getWife("超越韦小宝的老婆");
于是,结果就是:
众人:这与“柯南”童鞋有嘛关系?
① 柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
回答:莫急莫急,且听臣妾一曲。百科上的定义是针对众多函数式语言而言的,按照Stoyan Stefanov(《JavaScript Pattern》作者)的说法,所谓“柯里化”就是使函数理解并处理部分应用,套用上面官员例子,就是只要有合法老婆和见不得的情妇就行,数目什么的随意。不过,如果这样理解,老婆的例子就不符合国情,反而更加契合的理解是“柯南”。
众人:哦?洗耳恭听!
回答:柯南身子虽小,但是里面住的却是大柯南,也就是一个function
里面还有个function
。不同柯南处理不同情况,例如,小柯南可以和…稍等,他女朋友叫什么的忘了,我查查…哦,毛利兰一起洗澡澡;但是大柯南就不行。小柯南不能当面指正犯人,需借助小五郎;但是,大柯南就可以直接质问指出凶手。就类似于,内外function
处理不同的参数。如果代码表示就是(小柯南=smallKenan
; 大柯南=bigKenan
; 小柯南嗑药会变大柯南):
var smallKenan = function(action) { var bigKenan = function(doing) { var result = ""; if (action === "take drugs") { if (doing === "bathWithGirlFriend") { result = "尖叫,新一,你这个色狼,然后一巴掌,脸煮熟了~"; } else if (doing === "pointOutKiller") { result = "新一,这个案子就交给你的,快点找出谁是凶手吧~"; } } else { if (doing === "bathWithGirlFriend") { result = "来吧,柯南,一起洗澡吧~"; } else if (doing === "pointOutKiller") { result = "小孩子家,滚一边去!"; } } console.log(result); return arguments.callee; // 等同于return bigKenan }; return bigKenan; }; // 小柯南吃药了,然后和毛利兰洗澡,凶案现场指证犯人;结果是…… smallKenan("take drugs")("bathWithGirlFriend")("pointOutKiller");
结果如下截图:
“吃药”、“洗澡”、“指出凶手”就可以看成三个参数,其中,“吃药”确实是小柯南使用的,而后面的是“洗澡”、“指出凶手”虽然跟在smallKenan()
后面,实际上是大柯南使用的。这个就是柯里化,参数部分使用。外部函数处理部分应用,剩下的由外部函数的返回函数处理。
所以,我说“柯里化==柯南”不是胡扯吧~~
众人:我们擦~~,博主扯淡的功力TM现在更上一层楼啦,说得还真那么回事!
二、柯里化有什么作用?
众人:整这么多神乎其神的东西,有个毛线用?意淫一些所谓的技术,为了技术而技术最让人诟病了,我老老实实if else
照样脚本跑得杠杠的!
回答:唉,大家所言极是。苍蝇蚊子嗡嗡不停也让人诟病,但是,也是有其存在的理由的,我们不妨看看~~
众人:
回答:貌似柯里化有3个常见作用:1. 参数复用;2. 提前返回;3. 延迟计算/运行。
1. “参数复用”上面已经展示过了,官员老婆的例子就是,无论哪个官员,都是需要一个合法老婆;通过柯里化过程,getWife()
无需添加这个多余的“合法老婆”参数。
2. “提前返回”,很常见的一个例子,兼容现代浏览器以及IE浏览器的事件添加方法。我们正常情况可能会这样写:
var addEvent = function(el, type, fn, capture) { if (window.addEventListener) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); } else if (window.attachEvent) { el.attachEvent("on" + type, function(e) { fn.call(el, e); }); } };
上面的方法有什么问题呢?很显然,我们每次使用addEvent
为元素添加事件的时候,(eg. IE6/IE7)都会走一遍if...else if ...
,其实只要一次判定就可以了,怎么做?–柯里化。改为下面这样子的代码:
var addEvent = (function(){ if (window.addEventListener) { return function(el, sType, fn, capture) { el.addEventListener(sType, function(e) { fn.call(el, e); }, (capture)); }; } else if (window.attachEvent) { return function(el, sType, fn, capture) { el.attachEvent("on" + sType, function(e) { fn.call(el, e); }); }; } })();
初始addEvent
的执行其实值实现了部分的应用(只有一次的if...else if...
判定),而剩余的参数应用都是其返回函数实现的,典型的柯里化。
3. “延迟计算”,一般而言,延迟计算或运行是没有必要的,因为一天花10块钱和月末花300块钱没什么本质区别——只是心里好受点(温水炖青蛙)。嘛,毕竟只是个人看法,您可能会不这么认为。举个例子,我每周末都要去钓鱼,我想知道我12月份4个周末总共钓了几斤鱼,把一些所谓的模式、概念抛开,我们可能就会下面这样实现:
var fishWeight = 0; var addWeight = function(weight) { fishWeight += weight; }; addWeight(2.3); addWeight(6.5); addWeight(1.2); addWeight(2.5); console.log(fishWeight); // 12.5
每次addWeight
都会累加鱼的总重量。
若是有柯里化实现,则会是下面这样:
var curryWeight = function(fn) { var _fishWeight = []; return function() { if (arguments.length === 0) { return fn.apply(null, _fishWeight); } else { _fishWeight = _fishWeight.concat([].slice.call(arguments)); } } }; var fishWeight = 0; var addWeight = curryWeight(function() { var i=0; len = arguments.length; for (i; i<len; i+=1) { fishWeight += arguments[i]; } }); addWeight(2.3); addWeight(6.5); addWeight(1.2); addWeight(2.5); addWeight(); // 这里才计算 console.log(fishWeight); // 12.5
众人:我勒个去,好高的楼啊,相比之下。
回答:确实,柯里化的实现似乎啰嗦了点。老妈的啰嗦显然不是用来消耗多余的口水的。这里的curryWeight
方法啰嗦的意义在于柯里化的复用。比方说,我还想知道平均每次钓货的重量,则:
var averageWeight = 0; var addWeight = curryWeight(function() { var i=0; len = arguments.length; for (i; i<len; i+=1) { averageWeight += arguments[i]/len; } }); addWeight(2.3); addWeight(6.5); addWeight(1.2); addWeight(2.5); addWeight(); // 这里才计算 console.log(averageWeight); // 3.125
虽然延迟计算听上去很高级,但是,恕我愚钝,我想破了脑袋也没想出哪种情况非要柯里化延迟计算实现才能显著提高性能。能想到的好处就是参数个数随意,比方说:
addWeight(2.3, 6.5); addWeight(1.2, 2.5);
也是可以的。
补充于翌日:经人提点,发现自己忘了个东西,ES5中的bind
方法,用来改变Function
执行时候的上下文(函数主体本身不执行,与call
/apply
直接执行并改变不同),本质上就是延迟执行。例如:
var obj = { "name": "currying" }, fun = function() { console.log(this.name); }.bind(obj); fun(); // currying
从IE6~8的自定义扩展来看,其实现的机制就是柯里化(不考虑执行时的新增参数):
if (!function() {}.bind) { Function.prototype.bind = function(context) { var self = this , args = Array.prototype.slice.call(arguments); return function() { return self.apply(context, args.slice(1)); } }; }
以上文章出处转发自-----------http://www.zhangxinxu.com/wordpress/2013/02/js-currying
如有侵权请联系删除