在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
返回的函数,再通过这个变量调用函数。