1.作用
call、bind、apply的作用都是改变函数执行时的上下文,也就是修改函数的this指向。
let name = '于家宝'
let obj = {
name: 'yujiabao',
say: function () {
console.log(this.name)
}
}
obj.say() // yujiabao
setTimeout(obj.say, 0) // 于家宝
从上面代码来看,say方法正常情况下由obj调用,this指向obj,输出是yujiabao。
但是放在setTimeout中,在定时器中是作为回调函数来执行的,因此回到主栈执行时是在全局执行上下文的环境中执行的,这时this指向window,所以输出于家宝。
我们实际上需要的是this指向obj,这时候就需要去修改this的指向了。
setTimeout(obj.say.bind(obj), 1000) // yujiabao
2.使用方法
2.1 apply
func.apply(thisArg,argsArray)
作用:修改函数的this指向和往函数传递参数,并调用该函数,相当于调用函数的时候临时修改一下this指向。
- thisArg:func函数的this指向。
- argsArray:传入func函数的参数,是一个数组,可以忽略。
返回值:无。
let name = '于家宝'
let obj = {
name: 'yujiabao',
say: function (a, b) {
console.log(`${this.name}今年${a + b}岁`)
}
}
// 修改this指向为window并调用该函数
obj.say.apply(this, [10, 8]) // 于家宝今年18岁
2.2 call
基本和apply相同用法,只是接收参数的方式不一样。
func.call(thisArg,arg1,arg2...)
作用:修改函数的this指向和往函数传递参数,并调用该函数,相当于调用函数的时候临时修改一下this指向。
参数:
- thisArg:func函数的this指向。
- arg1,arg2...:传入func函数的参数,可以多个,可以忽略。
返回值:无。
let name = '于家宝'
let obj = {
name: 'yujiabao',
say: function (a, b) {
console.log(`${this.name}今年${a + b}岁`)
}
}
// 修改this指向为window并调用该函数
obj.say.call(this, 10, 8) // 于家宝今年18岁
2.3 bind
func.bind(thisArg,arg1,arg2...)
作用:修改函数的this指向和往函数传递参数,但是不会调用该函数,而是返回一个新的函数,这就是创建一个永久修改this指向的函数。
参数:
- thisArg:创建的新函数的this指向。
- arg1,arg2...:传入func函数的参数,可以多个,可以忽略。
返回值:一个修改了this指向的新函数。
let name = '于家宝'
let obj = {
name: 'yujiabao',
say: function (a, b) {
console.log(`${this.name}今年${a + b}岁`)
}
}
// 修改this指向为window,但是不会直接调用而是返回一个新函数
let newSay = obj.say.bind(this, 10, 8)
newSay() // 于家宝今年18岁
3.区别
3.1 传参区别
3.1.1 apply
apply的传参是将所有参数封装在一个数组中传给函数。
obj.say.apply(this, [10, 8])
3.1.2 call
call的传参是一个一个单独往函数中去传参。
obj.say.call(this, 10, 8)
3.1.3 bind
bind的传参跟call类似,也是一个一个单独的往函数中传参,但是可以分批传参。
let name = '于家宝'
let obj = {
name: 'yujiabao',
say: function (a, b) {
console.log(`${this.name}今年${a + b}岁`)
}
}
// 先往新函数传一个固定参数 10
let newSay = obj.say.bind(this, 10)
// 现在就可以传后面的参数
newSay(8) // 于家宝今年18岁
newSay(9) // 于家宝今年19岁
3.2 调用区别
3.2.1 apply和call
call 和 apply 方法修改完函数的this指向后会立即调用该函数,相当于调用函数的时候临时修改一下this指向。
// 通过 apply 立即执行
obj.say.apply(this, [10, 8])
// 通过 call 立即执行
obj.say.call(this, 10, 8)
3.2.2 bind
bind方法修改完函数的this指向后不会调用该函数,而是将其返回为一个新的函数,我们通过调用这个新的函数去实现修改this指向的效果。
let newSay = obj.say.bind(this, 10)
newSay(8) // 于家宝今年18岁
4.相同
都运用于修改函数的this指向,如果不传入第一个参数或者传入undefined、null,this指向为全局对象。
5.应用场景
5.1 apply和call
apply和call都是临时修改函数的this指向,当一个对象没有某个方法,而其他对象有这个方法,就可以借助apply或者call去临时使用这个方法。
let obj1 = {
name: 'yujiabao',
say: function () {
console.log(`我叫${this.name}`)
}
}
let obj2 = {
name: '于家宝'
}
// 让obj2使用say方法
obj1.say.call(obj2) // 我叫于家宝
apply相比于call,如果函数参数数量不确定,可以使用arguments, arguments是一个伪数组,这个时候apply就会更好用一点。
再举个例子:
定义一个log方法去实现console.log()的功能。
最开始的写法:
function log(msg) { console.log(msg) } log(1) //1 log(1, 2) //1
上面的方法可以实现基本需求,但是当传入的参数不固定的时候,方法就失效了,这时候就可以考虑使用apply或者call方法,因为参数是不固定的,所以这里使用apply。
function log() { console.log.apply(console, arguments) // 不能用 console.log(arguments),因为arguments是数组,这样就会打印出来一个数组 [1,2] // 所以使用apply方法,就相当于 console.log() 然后传入参数 1 ,2 } log(1) //1 log(1, 2) //1 2
如果想在每个log信息之前加个(于家宝)前缀,比如:
log(1,2) // (于家宝) 1 2
就相当于log('于家宝',1,2),而arguments是一个伪数组,我们可以将其转换为数组,然后使用数组的方法在前面加上‘于家宝’:
function log() { let args = [].slice.call(arguments) args.unshift('于家宝') console.log.apply(console, args) } log(1) // 于家宝 1 log(1, 2) // 于家宝 1 2
5.2 bind
因为bind是返回一个新的函数而不是立即调用,所以可以用于某些条件触发后调用的回调函数。如果用call或者apply就直接触发了。
var button = document.getElementById('myButton')
var person = {
name: 'John',
handleClick: function () {
console.log('Button clicked by' + this.name)
}
}
button.addEventListener('click', person.handleClick.bind(person))
6.注意事项
6.1 连续使用多次bind
let obj = {
name: 'yujiabao',
say: function () {
console.log(`我叫${this.name}`)
}
}
let name = '于家宝'
let newObj = obj.say.bind(this).bind(obj)
newObj() // ?
上面代码输入的结果:于家宝。
而不是预想中的yujiabao。原因是,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。
6.2 连续使用多次call或者apply
会报错,用完一次以后又不是返回个函数哪来的第二次调用。