call、bind、apply的区别
1.作用
2.使用方法
2.1 apply
2.2 call
2.3 bind
3.区别
3.1 传参区别
3.1.1 apply
3.1.2 call
3.1.3 bind
3.2 调用区别
3.2.1 apply和call
3.2.2 bind
4.相同
5.应用场景
5.1 apply和call
5.2 bind
6.注意事项
6.1 连续使用多次bind
6.2 连续使用多次call或者apply
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
会报错,用完一次以后又不是返回个函数哪来的第二次调用。