JS_函数柯里化
与函数绑定密切相关的主体是函数柯里化(
function currying
),它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:受用一个闭包返回一个函数。两者的区别在于,当函数被调用的时候,返回的函数还需要设置一些传入的参数。eg:
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()
版本。尽管从技术上来说
currieAdd()
并非是柯里化函数,但它很好地展示了这一概念。
柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式:
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()的第一个参数是要进行柯里化的函数,其他参数是要传入的值。
没有考虑到执行环境,所以调用apply时的第一个是null。curry()函数可以按照以下方式应用。
function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3));
首先,创建了第一个参数,绑定为5的add()的柯里化版本。当调用curriedAdd()并传入3时,3会成为add的第二个参数,最后结果便是8。当然也可以像下面例子这样给出所有的函数参数。
function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add, 5, 12);
alert(curriedAd()); //17
在这里,柯里化的add()函数两个参数都提供了,所以以后就无需再传递它们了。
- curry还可以实现函数连缀调用(使用桥接模式)
function curry(fn) {
var arr = [];
return function () {
if (arguments.length > 0) {
arr = arr.concat(Array.from(arguments));
return arguments.callee;
} else {
return fn.apply(null, arr);
}
}
}
// 桥接模式
var fn = curry(function () {
var arr = Array.from(arguments);
var sum = arr.reduce(function (value, item) {
return value + item;
});
return sum;
})
fn(10)(20)(30);
var sum = fn();
console.log(sum); //60
- 当然还可以写在prototype原型上
Function.prototype.curry = function () {
var arr = [];
var self = this;
return function () {
if (arguments.length > 0) {
arr = arr.concat(Array.from(arguments));
return arguments.callee;
} else {
return self.apply(null, arr);
}
}
}
function getSum() {
var arr = Array.from(arguments);
var sum = arr.reduce(function (value, item) {
return value + item;
});
return sum;
}
var fn = getSum.curry();
fn(10)(20);
fn(30, 40, 50);
fn(100);
var sum = fn();
console.log(sum); //250
函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的bind()函数。eg:
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()函数的主要更改在于传入的参数个数,当使用bind()时,它会返回绑定到给定环境的函数。
当你想除了event对象再额外给事件处理程序传递参数时,这非常有用,例如:
var handler = {
message:"Event handled",
handleClick:function(name, event){
alert(this.message + ":" + name + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));
- EventUtil中的addHandler()方法
addHandler: function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
}
在这个更新过的例子中,
handle.handleClick()
方法接受了两个参数:要处理的元素的名字和event对象。作为第三个参数传递给bind()函数的名字,以被传递给了handler.handleClick()
,而handler.handleClick()
也会同时接收到event对象。
ES5的bind()方法也实现函数柯里化,只要在this的值之后再传入另一个参数即可。
var handler = {
message:"Event handled",
handleClick:function(name, event){
alert(this.message + ":" + name + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addhandler(btn, "click", hanlder.handleClick.bind(handler, "my-btn"));
JavaScript中的柯里化函数和绑定函数提供了强大的动态函数创建功能。使用bind()还是curry()要根据是否需要object对象来决定。它们能用于创建复杂的算法和功能,当然两者都不能滥用。