函数绑定
函数绑定创建一个函数,可以在特定的this环境中以指定参数调用另一个函数,常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。一个简单的bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动的传递过去。
- 基本语法(ES5定义了原生的bind方法,所以一般不用自己定义):
function bind(fn, cotext){//(函数, 上下文环境)
return function(){
return fn.apply(context, arguments);
};
}
- 样例:
var handler = {
message: "Event handled",
handleClick: function(){
alert(this.message);
}
};
var btn = document.getElementById("my_btn");
btn.addEventListener("click", handle.handleClick, true);//点击后返回undefined
样例中按钮点击后返回undefined,是因为this是指向btn而不是handle,使用函数绑定后:
btn.addEventListener("click", bind(handler.handleClick, handler), true);//点击后返回Event handled
效果和下面闭包一样:
btn.addEventListener("click", function(event){
handler.handleClick(event);
}, true);//点击后返回Event handled
也可以直接使用ES5提供的原生实现的bind()函数绑定:
btn.addEventListener("click", handler.handleClick.bind(handler), true);//点击后返回Event handled
- 被绑定函数与普通函数相比需要更多的内存,同时也因为多重函数调用稍微慢一点。但在要将某个函数指针(比如例子中的handler.handleClick)以值的形式进行传递,并且这个函数只能在特定的环境中执行时,绑定函数便很有用了。
函数柯里化
函数柯里化用于创建已经设置好的一个或多个参数的函数。基本方法和函数绑定是一样的:使用一个闭包返回一个函数,两者的区别在于,函数柯里化返回的函数还需要设置一些传入的参数。函数柯里化的用途是指定函数的不变参数,使得多次调用函数的时候不用重复传入相同的参数,就像Java里面的static变量,不过js是通过闭包实现的。
- 通用的柯里化函数(ES5的bind()函数同样可以实现柯里化,传入参数和curry()相同):
function curry(fn) {
var args = Array.prototype.slice.call(arguments,1);//只固定了一个参数
return function () {
var innerArgs = Array.prototype.slice.call(arguments),
finalArgs = args.concat(innerArgs);
return fn.apply(null,finalArgs);
};
}
//ES6
function curry(func,...oldval) {
return (...newval) => func(...oldval, ...newval);
}
- 使用:
//一个求和函数
function sum() {
var finalSum = 0;
for (var i = 0; i < arguments.length && typeof arguments[i] === "number"; i++){
finalSum = finalSum + arguments[i];
}
console.log(finalSum);
}
var newSum = curry(sum, 3);
//var newSum = sum.bind(sum, 3);//效果一样
sum(1,2);//3
newSum(1,2);//6
- 函数柯里化比较适合处理延迟执行函数的参数传递:
比如用js实现了某个动画:
function demo(divId) {
var div = document.getElementById(divId),
left = div.offsetLeft;
setTimeout(function () {
if (left <= 900){
div.style.setProperty("left",left+5+"px");
demo(divId);
}
}, 50)
}
上面的延时函数每次执行都要传递一个相同的div的id,可以像下面这样简化:
demo = curry(demo, "demo");//"demo"是要固定的div的ID
//demo = demo.bind(demo, "demo")//ES5的bind()函数也支持柯里化
demo();
function demo(divId) {
var div = document.getElementById(divId),
left = div.offsetLeft;
setTimeout(function () {
if (left <= 900){
div.style.setProperty("left",left+5+"px");
demo();//再次调用函数无需再传入参数
}
}, 50)
}
这样处理的好处除了不需要每次向函数传递参数,减少性能开支外,还可以实现随时随地调用函数而不需要函数间连续传递参数。
比如上面的那个js实现的动画我需要在不同的函数体内重复调用,假如不用柯里化的话,我们可能需要这么实现:
function demo2(divID){
......
demo(divID);
......
}
function demo3(divID){
......
demo(divID);
......
}
而函数柯里化之后,我们只需要这样实现:
function demo2(){
......
demo();
......
}
function demo3(){
......
demo();
......
}
这样我们就不需要再在两个函数之间传递重复的参数。
- 每个函数都会带来额外的开销,所以最好在必要的时候才使用函数柯里化和函数绑定,具体使用bind()还是curry()要根据是否需要object对象响应来决定的。
参考资料:《JavaScript高级程序设计》第三版