在JavaScript中,apply, call, bind是三个非常重要的方法,它们的一个重要作用是在调用函数时改变this的值。注意:箭头函数的this值无法修改。
- Function.prototype.apply()
- Function.prototype.call()
- Function.prototype.bind()
apply
假设我们有一个函数用来计算两数之和:
1 | function add(a, b) { |
我们可以正常调用这个函数:
1 | add(1, 2); // 3 |
也可以使用apply方法来调用这个函数,第一个参数是this的值,第二个参数是一个数组,数组中的元素是函数的参数。这里我们不需要this,所以第一个参数传入null。
1 | add.apply(null, [1, 2]); // 3 |
call
call方法和apply方法类似,只是参数的传递方式不同,call方法的参数是一个个传递的,而不是通过数组传递。还是以上面的求和函数为例,使用call方法调用,第一个参数是this的值,后面的参数是函数的参数。这里我们不需要this,所以第一个参数传入null。
1 | add.call(null, 1, 2); // 3 |
bind
bind的调用方式与call类似,参数也是一个一个传递的。但是与call和apply直接调用函数不同,bind会创建一个新的函数并返回该函数,所以在使用bind时,我们通常会用一个变量接收bind返回的函数,再通过这个变量调用函数。
1 | fn.call(thisValue, arg1, arg2, ...); |
还记得前面的例子吗?将对象方法赋值给普通函数时,this指向了全局对象,而全局对象上没有name属性,所以输出undefined。
1 | const person = { |
我们只要用bind修改一下this,让它绑定到person对象,就可以输出Philip了。
1 | const person = { |
call, apply, bind的使用场景
我们知道,函数中有一个隐含对象arguments,包含了函数调用时传入的所有参数。它是一个类数组对象,我们无法对它使用数组的方法,比如map, filter, reduce等。但是我们可以通过call, apply, bind方法将数组的方法应用到arguments上。
比如下面这个求和函数,在函数体内部,我们可以使用reduce方法来计算所有参数的和。
1 | function sum() { |
注意,上面的例子仅作演示只用,一个更加规范的写法是:
1 | function sum(...args) { |
再看一个class中使用bind的例子:
1 | class CountDown { |
上面的代码中,this.interval是一个定时器,每隔1秒调用this.decrement方法,但是setInterval这个函数比较特殊,当我们将一个函数传递给setInterval时,这个函数的this值会指向全局对象,所以this.count会输出undefined。详情请参考这里
解决方法有两种,一种是在start方法中使用bind方法,将this绑定到CountDown类。
1 | start() { |
另一种方法是将setInterval的回调函数改为箭头函数,因为箭头函数没有自己的this binding, 所以this指向其parent scope,也就是CountDown。个人推荐使用这种方法。
1 | start() { |
当然,也可以将decrement方法改为箭头函数,因为箭头函数没有自己的this binding, 所以this指向其parent scope,也就是CountDown。
1 | decrement = () => { |
Conclusion
apply和call的作用是改变函数的this值,apply的参数是一个数组,call的参数是一个个传递的。这里有一个小窍门,apply是a开头的,数组array也是a开头的,所以apply的参数是一个数组。bind传递参数的方法和call类似(这个经常被忽略),但是bind会创建一个新的函数并返回该函数,所以在使用bind时,我们通常会用一个变量接收bind返回的函数,再通过这个变量调用函数。