函数绑定
在JavaScript的高级技巧中有一种技术叫函数绑定。
函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。它常常与函数回调与事件处理程序一起使用,以便将函数作为变量传递时保留代码的
执行环境。
这里创建的函数就是通过绑定之后返回的函数,调用的另一个函数就是原始函数(被绑定的函数)。
bind()方法实现绑定
这个方法的主要作用就是将
函数绑定到某个对象上。当
在函数fn()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数,
(以函数调用的方式)调用这个新的函数将会把原始的函数fn()当作对象o的方法来调用,
传入新函数的任何实参都将传入原始函数。
//这个待绑定的原始函数
function fn (y) {
return this.x + y;
}
//这个将要绑定的对象
var o = {
x: 2
};
//将fn(绑定到对象o上,并返回一个新函数。
var g = fn.bind(o); //此时this的值就是对象o
//通过调用g(x)来调用o.fn(x)
g(2); //4 2 + 2 = 4
在将fn()函数绑定到对象o上时,this对象就指向了对象o,因为在绑定函数中的this.x的值就是对象o的属性x的值。
apply()方法实现绑定
以上例子是在确定bind()方法存在的情况下定义的,如果不能确定bind()方法是否存在,那么可以使用以下方法来绑定函数:
function bind (f, o) {
if (f.bind) { //如果bind()方法存在,使用bind()方法绑定函数并则返回新函数(f.bind(o))
return f.bind(o);
} else {
return function () {
return f.apply(o, argument); //如果bind()方法不存在,这样实现将fn()绑定到对象o上。
};
}
}
从以上可以看出,"f.apply(o, argument)"的效果与"f.bind(o)"的效果是一样的,其中,argument对象是内部函数的。
通过这个绑定函数的方法举例:
<body>
<input id="input1" type="button" value="提交">
<script>
function bind (f, o) {
if (f.bind) { //如果bind()方法存在,使用bind()方法绑定函数并则返回新函数(f.bind(o))
return f.bind(o);
} else {
return function () {
return f.apply(o, argument); //如果bind()方法不存在,这样实现将f绑定到对象o上。
};
}
}
var handler = {
message: "hello JavaScript",
handClick: function (e) {
alert(this.message + ":" + e.type);
}
};
var input1 = document.querySelector("#input1");
//this的值为handler,这样this就指向了handler对象
input1.addEventListener("click", bind(handler.handClick, handler)); //hello JavaScript:click
</script>
</body>
其中,将handler.handClick方法绑定到handler对象上,此时,this的值就是handler,那么this.message就是handler.message,这样保留了handler.handClick()的执行环境。
当然,如果直接使用bind()方法更简单,只需要将添加事件处理程序那步修改成以下的代码:
input1.addEventListener("click", handler.handClick.bind(hanlder)); //这样this的值就是hanlder。
函数绑定的使用环境:只要是将函数指针(也就是原始函数指针)以值的形式传递,同时该函数必须在特定的环境中执行,就可以使用函数绑定。它们主要用于事件处理程序、setTimeout()和setinterval()计时器等。
函数柯里化
函数柯里化的方法与函数绑定是一样的,唯一的区别在于
函数柯里化所创建的函数还需要传入一些参数。
由apply()方法实现
动态创建柯里化函数:调用另一个函数并为它传入要柯里化的函数和必要的参数。
function curry (fn) {
//接收外部函数传入的参数,且要curry()函数的第二个参数及以后的参数,第一个参数是对象。
var args = Array.prototype.slice.call(arguments, 1);
//返回一个匿名函数,就是将其赋值给新函数
return function () {
//接收外部函数传入的所有参数
var innerArgs = Array.prototype.slice.call(arguments);
//将外部、内部接收的参数拼接为一个数组
var finalArgs = args.concat(innerArgs);
//最后调用fn()函数,并将拼接后的数组作为参数传入。
return fn.apply(null, finalArgs);
}
}
//创建一个将要柯里化的函数,也就是原始函数
function oldSum (num1, num2) {
return num1 + num2;
}
//柯里化函数,并返回一个新函数
var newSum = curry(oldSum, 3); // newSum = function () {};
console.log(newSum(4)); //7 3 + 4 = 7
这里的函数经历了以下步骤:
第一步:柯里化函数,并返回一个新函数。“var oldSum = curry(oldSum, 3)”
1、args=[3],外部函数就是curry(),且将第二个参数"3"保留在了数组args中。我们知道arguments对象可以访问传入到函数中的多个实参,这里就“3”这个实参保留在了args数组中。
2、return function () {}
第二步:调用newSum(4)
1、innerArgs=[4],这里将内部函数newSum的参数"4"保留在innerArgs中。
2、在外部、内部函数的参数数通过操作符concat拼接成一个数组并保留在finalArgs中。
3、fn.apply(null, finalArgs) ==> fn(finalArgs) ==> oldSum(3, 4)
最后得出结果为7。
也可以为curry()函数传入多个实参:
var newSum = curry(oldSum, 4, 6)
conlole.log(newSum(2)); // 10
此时外部函数参数有两个分别为“4”和"6",通过新函数newSum()传入到原始函数oldSum()的实参有三个,但由于oldSum()只有两个形参,所以内部函数(也就是newSum)的实参被忽略,只传入给了原始函数“4”和“6”这两个参数。
由bind()方法实现柯里化函数
bind()方法不仅仅可以将函数绑定到一个对象上,它还有其它的作用:
除了第一个实参外,传入bind()方法的实参也会被绑定到this对象上,这里的this的值绑定到传入bind()方法的对象或函数上。bind()方法实现柯里化的方法就是:在this的值后面再传入另一个参数即可。bind()方法实现:
<body>
<input id="input1" type="button" value="提交">
<script>
function bind (f, o) {
if (f.bind) { //如果bind()方法存在,使用bind()方法绑定函数并则返回新函数(f.bind(o))
return f.bind(o);
} else {
return function () {
return f.apply(o, argument); //如果bind()方法不存在,这样实现将f绑定到对象o上。
};
}
}
var handler = {
message: "hello JavaScript",
handClick: function (e) {
alert(this.message + ":" + e.type);
}
};
var input1 = document.querySelector("#input1");
//this的值为handler,在其后面再传入一个参数,也就实现了柯里化。
input1.addEventListener("click", handler.handClick(handler, "input1")); //hello JavaScript:click
</script>
</body>
这里在this的值即“handler”之后再传入了一个参数“input1”,表示这个按钮。这样就实现了柯里化函数。
再举例:
var oldSum = function (x, y) {
return x + y;
};
//创建一个类似oldSum的函数,但this的值绑定到null。
//并且第一个参数绑定到1,这个新的函数只期望传入一个实参
var newSum = oldSum.bind(null, 1); //这里实参1被绑定到了x上
newSum(2); //3 x绑定到1,并传入2作为实参y。
function f (y, z) {
return this.x + y + z;
}
var g = f.bind({x: 1}, 2); //this的值绑定x,2绑定到y上。
g(4); //7 this.x绑定1,y绑定到2,z绑定到4。
再举例:
function oldSum (num1, num2) {
return num1 + num2;
}
var o = {
num1: 1
};
var newSum1 = oldSum.bind(this, 2); //this.oldSum(2) 实参2绑定到了形参num1上。
console.log(newSum1(3)); //5 num1绑定2,num2绑定3。
function oldSum (num1, num2) {
return num1 + num2;
}
var o = {
num1: 1
};
var newSum1 = oldSum.bind(this, 2,4); //this.oldSum(2) 实参2绑定到了形参num1上,4绑定到了num2上
console.log(newSum1(3)); //6 num1绑定2,num2绑定4,新函数的实参因没有形参绑定所以被忽略。
若此时,
原始函数的形参数量增加,那么
新函数的实参也会被绑定到原始函数的形参上:
function oldSum (num1, num2, num3) {
return num1 + num2j + num3;
}
var o = {
num1: 1
};
var newSum1 = oldSum.bind(this, 2, 4); //this.oldSum(2) 实参2绑定到了形参num1上,4绑定到num2上
console.log(newSum1(3)); //9 num1绑定2,num2绑定4,num3绑定3
再接着,传入bind()方法的实参减少一个:
function oldSum (num1, num2, num3) {
return num1 + num2j + num3;
}
var o = {
num1: 1
};
var newSum1 = oldSum.bind(this, 2); //this.oldSum(2) 实参2绑定到了形参num1上。
console.log(newSum1(3, 5)); //10 num1绑定2,num2绑定2,num3绑定5
从以上几个例子可以看出:传入bind()方法的实参优先绑定到绑定函数(相当于原始函数)的形参上。如果传入bind()方法的实参个数多于绑定函数的形参个数,一一对应,则忽略最后的几个实参;如果传入bind()方法的实参个数少于绑定函数形参的个数,则实参绑定到前几个形参,剩下的形参绑定新函数传入的实参,若此时新函数传入的实参多个剩下形参的个数,一一对应,则忽略新函数的最后几个实参。
bind()方法返回一个函数对象,这个函数的
length值(新函数可以传入的实参的个数)是
绑定函数的形参个数减去绑定函数的实参(也就是bind()方法传入的实参个数)个数。
function oldSum (num1, num2, num3) {
return num1 + num2j + num3;
}
var o = {
num1: 1
};
var newSum1 = oldSum.bind(this, 2); //this.oldSum(2) 实参2绑定到了形参num1上。
console.log(newSum1(3, 5)); //10 num1绑定2,num2绑定2,num3绑定5
其中,绑定函数的形参个数是3,绑定函数的实参个数(也就是bind()方法传入的实参个数)为1,那么新函数可以传入的实参个数就是3-1=2个,也就是说可以传入"3"和"5"这两个实参。
如果 bind()方法返回的函数作用构造函数,将会忽略传入bind()的this,原始函数就会以构造函数形式调用,实参会原封不动地传入原始函数。由bind()方法返回的函数并不包含prototype属性,由bind()方法返回的函数作为构造函数所创建的对象会从未绑定的函数那里继承prototype属性。
函数绑定与函数柯里化的区别:柯里化就是在绑定函数的基础上,在this值之后再传入实参。