未加星标

邂逅函数柯里化

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二05 | 时间 2017 | 作者 红领巾 ] 0人收藏点击收藏
引子

有这样一道题目,实现一个函数,实现如下功能:

var result = sum(1)(2)(3); console.log(6);//6

这道题目,印象中是一道技术笔试题。结合查到的资料,在这里做一下简单的分析和总结。

一个简单的例子

题目给的还是比较宽的,没多少限制,给了很多自由发挥的空间。

下面我们就一步一步的去实现,一种简单的做法可以是这样的:

function add(a){ var sum = 0; sum += a; return function(b){ sum += b; return function(c){ sum += c; return sum; } } } add(1)(2)(3);//6

嗯,没什么问题。

在此基础上,我们再进一步:

如果对调用的次数不加限制,比如 四次,那上面的代码就不行了。 那该怎么办呢?

观察一下,我们可以发现返回的每一个函数执行的逻辑其实都是一样的。

就此我们可以精简下代码,让函数返回后返回自身。

来试一下:

function add(a){ var sum = 0; sum += a; return function temp(b) { sum += b; return temp; } } add(2)(3)(4)(5); 输出的结果: //function temp(b) { // sum += b; // return temp; // }

并没有像我们预期的那样输出 14,其实是这样的,每次函数调用后返回的就是一个函数对象,那最后的结果,肯定是一个字符串表示啊。

要修正的话,有两个办法。

判断参数,当没有输入参数时,返回调用结果:

function add(a){ var sum = 0; sum += a; return function temp(b) { if (arguments.length === 0) { return sum; } else { sum= sum+ b; return temp; } } } add(2)(3)(4)(5)(); //14

如果要使用匿名函数,也可以:

function add() { var _args = []; return function(){ if(arguments.length === 0) { return _args.reduce(function(a,b) { return a + b; }); } [].push.apply(_args, [].slice.call(arguments)); return arguments.callee; } } var sum = add(); sum(2,3)(4)(5)(); //14

2 . 利用JS中对象到原始值的转换规则。

当一个对象转换成原始值时,先查看对象是否有valueOf方法。

如果有并且返回值是一个原始值,那么直接返回这个值。

如果没有valueOf 或 返回的不是原始值,那么调用toString方法,返回字符串表示。

那我们就为函数对象添加一个valueOf方法 和 toString方法:

function add(a) { var sum = 0; sum += a; var temp = function(b) { if(arguments.length===0){ return sum; } else { sum = sum+ b; return temp; } } temp.toString = temp.valueOf = function() { return sum; } return temp; } add(2)(3)(4)(5); //14

写到这里,我们来简单总结下。

柯里化的定义

柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后,部分应用参数,并返回一个更具体的函数接受剩下的参数,中间可嵌套多层这样的接受部分参数函数,逐步缩小函数的适用范围,逐步求解,直至返回最后结果。

一个通用的柯里化函数 var curring = function(fn){ var _args = []; return function cb(){ if(arguments.length === 0) { return fn.apply(this, _args); } Array.prototype.push.apply(_args, [].slice.call(arguments)); return cb; } } var multi = function(){ var total = 0; var argsArray = Array.prototype.slice.call(arguments); argsArray.forEach(function(item){ total += item; }) return total }; var calc = curring(multi); calc(1,2)(3)(4,5,6); console.log(calc()); //空白调用时才真正计算

这样 calc = currying(multi),调用非常清晰.

如果要 累加多个值,可以把多个值作为做个参数 calc(1,2,3),也可以支持链式的调用,如 calc(1)(2)(3);

到这里, 不难看出,柯里化函数具有以下特点:

函数可以作为参数传递

函数能够作为函数的返回值

闭包

说了这么多,柯里化函数到底能够帮我做什么,或者说,我为什么要用柯里化函数呢? 我们接着往下看。

柯里化函数的作用

函数柯里化允许和鼓励你分隔复杂功能变成更小更容易分析的部分。这些小的逻辑单元显然是更容易理解和测试的,然后你的应用就会变成干净而整洁的组合,由一些小单元组成的组合。

1.提高通用性

function square(i) { return i * i; } function dubble(i) { return i *= 2; } function map(handeler, list) { return list.map(handeler); } // 数组的每一项平方 map(square, [1, 2, 3, 4, 5]); map(square, [6, 7, 8, 9, 10]); map(square, [10, 20, 30, 40, 50]); // ...... // 数组的每一项加倍 map(dubble, [1, 2, 3, 4, 5]); map(dubble, [6, 7, 8, 9, 10]); map(dubble, [10, 20, 30, 40, 50]);

例子中,创建了一个map通用函数,用于适应不同的应用场景。显然,通用性不用怀疑。同时,例子中重复传入了相同的处理函数:square和dubble。

应用中这种可能会更多。当然,通用性的增强必然带来适用性的减弱。但是,我们依然可以在中间找到一种平衡。

看下面,我们利用柯里化改造一下:

function currying(fn) { var slice = Array.prototype.slice, __args = slice.call(arguments, 1); return function () { var __inargs = slice.call(arguments); return fn.apply(null, __args.concat(__inargs)); }; } function square(i) { return i * i; } function dubble(i) { return i *= 2; } function map(handeler, list) { return list.map(handeler); } var mapSQ = currying(map, square); mapSQ([1, 2, 3, 4, 5]); //[1, 4, 9, 16, 25] var mapDB = currying(map, dubble); mapDB([1, 2, 3, 4, 5]); //[2, 4, 6, 8, 10]

我们缩小了函数的适用范围,但同时提高函数的适性.

2 延迟执行。

柯里化的另一个应用场景是延迟执行。不断的柯里化,累积传入的参数,最后执行。

3.固定易变因素。

柯里化特性决定了它这应用场景。提前把易变因素,传参固定下来,生成一个更明确的应用函数。最典型的代表应用,是bind函数用以固定this这个易变对象。

Function.prototype.bind = function(context) { var _this = this, _args = Array.prototype.slice.call(arguments, 1); return function() { return _this.apply(context, _args.concat(Array.prototype.slice.call(arguments))); } }

Function.prototype.bind 方法也是柯里化应用与 call/apply 方法直接执行不同,bind 方法 将第一个参数设置为函数执行的上下文,其他参数依次传递给调用方法(函数的主体本身不执行,可以看成是延迟执行),并动态创建返回一个新的函数, 这符合柯里化特点。

var foo = { x: 666 }; var bar = function () { console.log(this.x); }.bind(foo); // 绑定 bar(); //666 // 下面是一个 bind 函数的模拟,testBind 创建并返回新的函数,在新的函数中将真正要执行业务的函数绑定到实参传入的上下文,延迟执行了。 Function.prototype.testBind = function (scope) { var self = this; // this 指向的是调用 testBind 方法的一个函数, return function () { return self.apply(scope); } }; var testBindBar = bar.testBind(foo); // 绑定 foo,延迟执行 console.log(testBindBar); // Function (可见,bind之后返回的是一个延迟执行的新函数) testBindBar(); // 666 关于curry性能的备注

通常,使用柯里化会有一些开销。取决于你正在做的是什么,可能会或不会,以明显的方式影响你。也就是说,几乎大多数情况,你的代码的拥有性能瓶颈首先来自其他原因,而不是这个。

有关性能,这里有一些事情必须牢记于心:

存取arguments对象通常要比存取命名参数要慢一点.

一些老版本的浏览器在arguments.length的实现上是相当慢的.

创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上.

以上 ;)

参考资料

http://blog.jobbole.com/77956/

http://www.cnblogs.com/pigtai...

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: 高通浏览器其实常清
分页:12
转载请注明
本文标题:邂逅函数柯里化
本站链接:http://www.codesec.net/view/530381.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(13)