您的位置:首页 > Web前端 > JavaScript

函数柯里化

2016-05-30 11:38 429 查看
在介绍函数柯里化之前,先来学一下函数绑定。

函数绑定

函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数,该技巧常常和回调函数与事件处理程序一起调用,以便在将函数作为变量传递的同时保留代码执行环境。看个例子:

var handler = {
message: "Event handled",

handleClick: function  (event) {
alert(this.message);
}
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick);


在上面的例子中,创建了一个叫做handler的对象。handler.handleClick()方法被分配为一个DOM按钮的事件处理程序,当按下该按钮时就调用该函数,显示一个警告框,虽然貌似警告框应该显示Event handled,然而实际上显示的是undefined。这个问题在于没有保存handler.handleClick()的环境,所以this对象最后是指向 dom按钮而非handler可以如下面例子修正这个问题:

var handler = {
message: "Event handled",

handleClick: function  (event) {
alert(this.message);
}
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", function  (event) {
handler.handlerClick(event);
})


这个解决方案在onclick事件处理程序内使用了一个闭包直接调用handler.handleClick(),当然,这是特定于这段代码的解决方案。创建多个闭包可能会令代码难于理解和调试,因此,很多javaScript库实现了一个可以将函数绑定到指定环境的函数,这个函数一般叫bind(),一个简单的bind函数接收一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动的传递进去。

function bind (fn, context) {
return function  () {
return fn.apply(context, arguments);
}
}


这个函数似乎简单,但其功能是非常强大的,在bind()中创建一个闭包,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数。注意这里使用的arguments对象是内部函数的,而非bind()的,当调用返回的函数时,他会在给定环境中执行被传入的函数并给出所有参数。bind()函数按如下方式使用

var handler = {
message: "Event handled",

handleClick: function  (event) {
alert(this.message);
}
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handled.handlerClick, handler));


在这个例子中,我们用bind()函数创建了一个保持了执行环境的函数,并将其传给EventUtil.addHandler()。event对象也被传给了该函数,如下所示

var handler = {
message: "Event handled",

handleClick: function  (event) {
alert(this.message+":"+event.type);
}
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handled.handlerClick, handler));


handler.handleClick()方法和平时一样获得了event对象,因为所有的参数都通过函数被绑定的函数直接传给了它。

es5为所有函数定义了一个原生的bind()方法,换句话说,你不用再自己定义bind()函数了,而是可以直接在函数上调用这个方法,例如:

var handler = {
message: "Event handled",

handleClick: function  (event) {
alert(this.message+":"+event.type);
}
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handlerClick.bind(handler));


原生的bind()方法与前面介绍的自定义bind()方法类似,都是要传入作为this值的对象。

只要是将某个函数指针以值的方式进行传递,同时该函数必须在特定环境中执行,被绑定的函数效用就突显出来了。他们主要用于事件处理程序以及setTimeout()和setInterval(),然而,被绑定函数与普通函数相比有更多的开销,他们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时调用。

函数柯里化

与函数绑定紧密相关的主题是函数柯里化,他用于创建已经设置好了一个或多个参数的函数,函数柯里化的基本方法和函数绑定是一样的,使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数,

function add (num1, num2) {
return num1 + num2;
}
function curriedAdd (num2) {
return add(5, num2);
}
alert(add(2, 3))     //5
alert(curriedAdd(3));//8


这段代码定义了两个函数add和curriedAdd(),后者本质上是在任何情况下第一个参数为5的add()版本,尽管从技术上来说curriedAdd()并非柯里化的函数,但它很好的展示了其概念。下面是创建柯里化函数的通用方式

function curry (fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function  () {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
}
}


curry()函数的主要工作就是将被返回函数的参数进行排序,curry()的第一个参数是要进行柯里化的函数,其他参数是要传入的值,为了获取第一个参数之后的所有参数,在argumens对象上调用了slice()方法,并传入了参数1表示被返回的数组包含从第二个参数开始的所有参数,然后args数组包含了来自外部的函数的参数。在内部函数中,创建了innerArgs数组用来存放所有传入的参数,(又一次用到了slice),有了存放来自外部函数和内部函数的参数数组后,就可以使用concat()方法将他们组合为finalArgs,然后使用apply()将结果传递给该函数,注意这个函数并没有考虑到执行环境,所以调用apply()时第一个参数是null,curry()函数就可以按以下方式应用。

function add (num1, num2) {
return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3));           //8


在这个例子中,创建了第一个参数绑定为5的add()的柯里化版本。当调用curriedAdd()并传入3时,3会成为add()的第二个参数,同时第一个参数仍然是5,最后结果便是和8,你也可以像下面例子这样给出所有的函数参数:

function add (num1, num2) {
return num1 + num2;
}
var curriedAdd = curry(add, 5, 12);
alert(curriedAdd());           //17


在这里,柯里化add()函数两个参数都提供了,所以以后就无需再传递他们了。

函数柯里化还常常作为函数绑定的一部分包含其中,构造出更为复杂的bind()函数了,例如:

function bind (fn, context) {
var args = Array.prototype.slice.call(arguments, 2);
return function  () {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(context, finalArgs);
}
}


对curry()函数的主要更改在于传入的参数个数,以及它如何影响代码的结果,curry()仅仅接收一个要包裹的函数作为参数,而bind()函数同时接收函数和一个object对象。这表示给被绑定的函数的参数是从第三个开始而不是第二个,这就要更改slice()的第一个调用,另一处更改是在倒数第三行将object对象传给了apply()当使用bind()时。他会返回绑定要给定环境的函数,并且可能他其中某些函数参数已经设定好,当你想除了event对象在额外给事件处理程序传递参数时,这非常有用,例如

var handler = {
message: "Event handled",

handleClick: function  (name, event) {
alert(this.message+":"+event.type);
}
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));


在这个更新的例子中,handler.handleClick()方法接受了两个参数:要处理的元素的名字和event对象,作为第三个参数传递给bind()函数的名字,又被传递给了,handler.handleClick().而handler.handleClick()也会同时接收到event对象,es5的bind()方法也实现函数柯里化,只要在this的值之后再传入另一个参数即可。

var handler = {
message: "Event handled",

handleClick: function  (name, event) {
alert(this.message+":"+event.type);
}
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));


javaScript中的柯里化函数和绑定函数提供改了强大的动态函数创建功能,使用bind()还是curry()要根据是否需要object对象响应开决定,他们都能用于创建复杂的算法和功能,当然两者都不应滥用,因为每个函数都会带来额外的开销。

柯里化的特点是: 降低通用性,提高专用性。,函数柯里化给我们带来的是:解决问题的一种逻辑思维方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息