函数式编程是一种被部分JavaScript程序员推崇的编程风格,更别说 Haskell 和 Scala 这种以函数式为教义的语言。缘由是由于其能用较短的代码实现功能,若是掌握得当,能达到代码文档化(代码自己具备很高可读性甚至能够代替文档)的效果,固然,泛滥使用也会使代码可读性变差。程序员
而柯里化(Currying)是一个属于函数式编程的一个常见的技巧。简单来讲,函数柯里化就是对高阶函数的降阶处理。
咱们看下面这个函数:编程
function loc (a,b,c){ console.log(a+'-'+b+'-'+c); } loc('浙江','杭州','西湖区');//>>>浙江-杭州-西湖区
这是一个接收三个地名字符串的函数,功能是将三个地名字符串拼接到一块儿成为一个详细的地址。
咱们试试只传入两个参数:segmentfault
loc('浙江','杭州');//>>>浙江-杭州-undefined
毫无疑义地,这个函数仍是会正常执行,只是本来属于“区”的位置因为没有接收到参数而成了undefined
。数组
其实这种状况不少,好比咱们还要生成浙江-杭州-余杭区
,浙江-杭州-拱墅区
这样的地名,可是咱们不必每次都从新把浙江-杭州
从新拼接一遍,因此你是否是会想,能不能只经过一个函数,把已经拼接过的字符串缓存起来,只去拼接新的字符串呢?咱们或许能够把以前的函数改一下:缓存
function loc (a) { return function(b){ return function(c){ console.log(a+'-'+b+'-'+c); } } }
好奇怪!这个loc函数只接收一个参数,而返回一个新的函数,这个函数也只接受一个参数,里面一样返回一个函数,最后一个函数才返回三个参数拼接的字符串。
看起来是一个嵌套关系,好吧,让咱们看看它是否能实现刚刚的想法:闭包
var Zhejiang = loc('浙江'); var Hangzhou = Zhejiang('杭州'); var Xihu = Hangzhou('西湖区'); //浙江-杭州-西湖区 var Yuhang = Hangzhou('余杭区'); //浙江-杭州-余杭区 var Lucheng = Zhejiang('温州')('鹿城区'); //浙江-温州-鹿城区
看,经过这样的形式,咱们轻松实现定制化函数啦!loc('杭州')
不会急着把地名都拼接好,而是把杭州
先存到闭包中,在须要拼接的时候才用到它。app
让你意外的是,这就是柯里化的基本思想,简单地让人猝不及防。函数式编程
不,这不是你想要的结果,至少你已经考虑到一种恐怖的状况:若是参数有许多——好比100个,是否是要写一个嵌套一百次的函数?函数
好在,咱们能够写一个通用函数来优雅地建立柯里化函数:spa
function curry(fn) { var outerArgs = Array.prototype.slice.call(arguments, 1); return function() { var innerArgs = Array.prototype.slice.call(arguments), finalArgs = outerArgs.concat(innerArgs); return fn.apply(null, finalArgs); }; }
有了这个基本函数以后,咱们能够柯里化其余普通函数:
//一个普通函数 function loc(a,b,c){ console.log(a+'-'+b+'-'+c); } var workIn = curry(loc,'浙江','杭州','余杭区'); workIn();// >>> 浙江-杭州-余杭区
固然也能够这样定制:
var zj = curry(loc,'浙江'); var city = curry(zj,'杭州'); city('余杭区'); //>>> 浙江-杭州-余杭区 city('上城区'); //>>> 浙江-杭州-上城区 zj('温州','鹿城区');//>>> 浙江-温州-鹿城区
简直优雅。
如下咱们来简单分析如下这个通用函数:
function curry(fn) { var outerArgs = Array.prototype.slice.call(arguments, 1); return function() { var innerArgs = Array.prototype.slice.call(arguments), finalArgs = outerArgs.concat(innerArgs); return fn.apply(null, finalArgs); }; }
咱们看这个curry
函数,显示接受一个参数,那就是须要被柯里化的函数。同时,由于JS神奇的函数传参,咱们能够在curry
继续放更多的参数,这些参数在函数体内部将以参数列表的形式存在,你能够经过arguments
引用这个参数列表。注意,arguments
引用的是一个参数列表而不是数组(虽然很像),因此数组的不少方法它都没有。固然,这个难不倒咱们,还记得我上一篇文章里提到的apply()
黑科技吗?传送门:《快速理解JavaScript中apply()和call()的用法和用途》
outerArgs
就是获取除了第一个参数以外的参数列表。
而在返回的函数里,innerArgs
获取的是调用这个返回函数时传入的全部参数,好比以前city('上城区')
里面的'上城区'。
而finalArgs
则是拼接这两个数组(注意此时两个参数列表都已是正宗的数组,因此才可使用concat
方法)。
最后,使用apply
,把全部参数组成的列表传入到原来的函数,其实质就是在调用原始的函数,由于此时的参数都已齐全。
结尾福利: