不可或缺的curry
curry的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数、
你可以一次性地调用curry函数,也可以每次只传一个参数分多次调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var add = function(x) { return function(y) { return x + y; }; };
var increment = add(1); var addTen = add(10);
increment(2);
addTen(2);
|
这里我们定义了一个 add 函数,它接受一个参数并返回一个新的函数。调用 add 之后,返回的函数就通过闭包的方式记住了 add 的第一个参数。一次性地调用它实在是有点繁琐,好在我们可以使用一个特殊的 curry 帮助函数使这类函数的定义和调用更加容易。
下面是一下curry函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var curry = require('lodash').curry;
var match = curry(function(what, str) { return str.match(what); });
var replace = curry(function(what, replacement, str) { return str.replace(what, replacement); });
var filter = curry(function(f, ary) { return ary.filter(f); });
var map = curry(function(f, ary) { return ary.map(f); });
|
我在上面的代码中遵循的是一种简单,同时也非常重要的模式。即策略性地把要操作的数据(String, Array)放到最后一个参数里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| match(/\s+/g, "hello world");
match(/\s+/g)("hello world");
var hasSpaces = match(/\s+/g);
hasSpaces("hello world");
hasSpaces("spaceless");
filter(hasSpaces, ["tori_spelling", "tori amos"]);
var findSpaces = filter(hasSpaces);
findSpaces(["tori_spelling", "tori amos"]);
var noVowels = replace(/[aeiou]/ig);
var censored = noVowels("*");
censored("Chocolate Rain");
|
这里表明的是一种“预加载”函数的能力,通过传递一到两个参数调用函数,就能得到一个记住了这些参数的新函数。
个人觉得:“柯里化”就像某些官员的把戏,官员要弄7个老婆,碍于国策(一夫一妻)以及年老弟衰,表面上就1个老婆,实际上剩下的6个暗地里消化。代码表示就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| var currying = function(fn) { var args = [].slice.call(arguments, 1); return function() { var newArgs = args.concat([].slice.call(arguments)); return fn.apply(null, newArgs); }; };
var getWife = currying(function() { var allWife = [].slice.call(arguments); console.log(allWife.join(";")); }, "合法老婆");
getWife("大老婆","小老婆","俏老婆","刁蛮老婆","乖老婆","送上门老婆");
getWife("超越韦小宝的老婆");
|
柯里化的作用
参数复用
参数复用”上面已经展示过了,官员老婆的例子就是,无论哪个官员,都是需要一个合法老婆;通过柯里化过程,getWife()无需添加这个多余的“合法老婆”参数
提前返回
“提前返回”,很常见的一个例子,兼容现代浏览器以及IE浏览器的事件添加方法。我们正常情况可能会这样写:
1 2 3 4 5 6 7 8 9 10 11
| 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 …,其实只要一次判定就可以了,怎么做?–柯里化。改为下面这样子的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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…判定),而剩余的参数应用都是其返回函数实现的,典型的柯里化。
延迟执行
1 2 3 4 5 6 7 8
| Function.prototype.bind = function (context) { var _this = this var args = Array.prototype.slice.call(arguments, 1) return function() { return _this.apply(context, args) } }
|
像我们js中经常使用的bind,实现的机制就是Currying.