转载

JavaScript也玩私人订制——玩转函数柯里化

函数式编程是一种被部分JavaScript程序员推崇的编程风格,更别说 Haskell 和 Scala 这种以函数式为教义的语言。原因是因为其能用较短的代码实现功能,如果掌握得当,能达到 代码文档化 (代码本身具有很高可读性甚至可以代替文档)的效果,当然,泛滥使用也会使代码可读性变差。

柯里化(Currying) 是一个属于函数式编程的一个常见的技巧。简单来说,函数柯里化就是对高阶函数的降阶处理。

我们看下面这个函数:

function loc (a,b,c){     console.log(a+'-'+b+'-'+c); }  loc('浙江','杭州','西湖区');//>>>浙江-杭州-余杭区

这是一个接收三个地名字符串的函数,功能是将三个地名字符串拼接到一起成为一个详细的地址。我们试试只传入两个参数:

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('杭州') 不会急着把地名都拼接好,而是把 杭州 先存到闭包中,在需要拼接的时候才用到它。

让你意外的是,这就是柯里化的基本思想,简单地让人猝不及防。

不,这不是你想要的结果,至少你已经考虑到一种恐怖的情况:如果参数有许多——比如100个,是不是要写一个嵌套一百次的函数?

好在,我们可以写一个通用函数来优雅地创建柯里化函数:

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 ,把所有参数组成的列表传入到原来的函数,其实质就是在调用原始的函数,因为此时的参数都已齐全。

结尾福利:

原文  https://segmentfault.com/a/1190000004589338
正文到此结束
Loading...