apply
, call
和bind
都是用来动态修改函数执行环境的方法,它们都可以让函数绑定指定的this
。
this
是函数用来获取到它的原型的属性和方法的关键字。当它在函数中使用时,总是指向一个对象——就是调用这个函数的对象。但有时候,this
的指向会发生偏差,这时就需要我们人为地为this
绑定对象。
举一个栗子:
1 | // 当购物车中有东西时,就给这个商品添加一个“删除”按钮 |
因为在deleteBtn.addEventListener
里面,this
指向的是deleteBtn
这个对象,而不再是myCart
了,如果我们需要使用myCart
的方法,有一种方法是提前将我们要使用的this
保存在一个变量中,需要用到的时候使用变量来替代。
但是相比使用变量,我们还有更好,更优雅的方法。
bind()
MDN的解释:
bind()
方法创建一个新的函数,当这个函数被调用时,它的this
关键字被设置成一个特定的值,新函数被调用时,还可以传递一组序列参数。
我们先看一个简单的示例,它说明了什么情况下我们会需要用到bind
来修改this
的指向。
1 | var module = { |
触发函数时的上下文并不是我们想要引用this
的上下文,这时就需要给this
绑定我们希望执行的上下文。
看一个语义化更明显的例子:
1 | var pokemon = { |
bind()
函数实际上是创建了一个新的pokomonName
的实例,并将pokemon
作为this
值,这样新的实例就拥有了所有pokemonName
函数的方法的副本。
原理
bind()
函数创建了一个新的绑定函数(bound function, BF)。这个绑定函数将原来的函数对象包裹起来。所以在调用绑定函数时,实际上是在执行被包裹的函数。绑定函数有如下属性:
[[BoundTargetFunction]]
即被包裹的函数对象[[BoundThis]]
当调用被包裹的函数时,这个值总是被传递给被包裹函数作为this
值[[BoundArguments]]
一组值,作为触发被包裹函数时的第一个参数[[Call]]
执行与此对象关联的代码。 通过函数调用的表达式被触发。 内部方法的参数是一个this值和一个包含通过调用表达式传递给函数的参数列表
当绑定函数被调用时,它调用的是[[BoundTargetFunction]]
上的内部方法[[Call]]
,后跟参数,像这样Call(boundThis, args)
。其中,boundThis
就是[[BoundThis]]
,args
就是[[BoundArguments]]
,后跟函数调用传递的参数。
绑定函数也可以用new
去构造一个由目标函数创建的新的实例。当一个绑定函数被用来创建新的实例时,原来提供的this
值会被忽略,但原来提供的参数仍然被前置到构造函数的调用里面。
1 | function Point(x, y) { |
此时如果我们打印axisPoint
的__proto__
就可以看到它的__proto__
原型就是Point
函数。
![image-20180916105705225](../../../Library/Application Support/typora-user-images/image-20180916105705225.png)
此外,bind()
函数可以插入固定参数
1 | function list() { |
window.setTimeout()
的this
默认是window
对象。当使用需要将this
指向类的实例的类方法时,可以直接将this
绑定到回调函数上,以此来维护实例。
1 | function lateBloomer() { |
bind()
可以用于简化函数。比如Array.prototype.slice
可以用来将一组array-like的数组转换成真数组,那么就可以这样写:
1 | var slice = Array.prototype.slice; |
使用bind()
还能进一步简化。下面的代码中,slice
是函数原型的apply()
方法的绑定函数,并且把this
指向数组原型的slice()
方法,这样多余的apply()
就可以省略掉了。
1 | var unboundSlice = Array.prototype.slice; |
兼容性问题
低版本的IE不支持bind()
,那么可以使用代码将bind()
绑定到环境里。
1 | if(!Function.prototype.bind) { //判断环境是否不支持bind |
call()
和apply()
call()
和apply()
方法的作用一样,语法基本一致,唯一的区别是call()
接受参数的方式是逐个传入,apply()
则是接受一组参数数组。
这两个方法都是调用一个函数,并且给这个函数指定一个this
值,同样也可以传入参数。
1 | function Product(name, price) { |
bind()
,apply()
和call()
这三个函数在修改函数执行的上下文时都能指定this
值,它们有一些细微的区别。
bind()
会返回一个函数,call()
和apply()
直接调用函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let obj = {things: 3};
let addThings = function(a, b, c){
return this.things + a + b + c;
}
//此时console.log(addThings())输出NaN,因为this指向window,this.things是undefined
//解决方案1:
console.log(addThings.call(obj, 1,2, 3))//9
//解决方案2:
let arr = [1, 2, 3]
console.log(addThings.apply(obj, arr)) //9
//解决方案3:
console.log(addThings.bind(obj, 1,2,3)); //[Function: bound addThings] 返回一个函数
console.log(addThings.bind(obj,1,2,3)()); // 9bind()
返回的函数是addThings
的副本,而apply()
和call()
在调用时不会创建副本
参考:
Javascript: call(), apply() and bind()