call、apply、bind的作用
call、apply、bind的作用都是函数执行时的上下文,也就是改变this指向。
下面代码,say()正常情况下由obj调用,所以this指向obj,this.name就是yujiabao。但是放在setTimeout中,在定时器中是作为回调函数来执行的,因此回到主栈执行时是在全局执行上下文的环境中执行的,这时this指向window,所以输出于家宝。
let name = '于家宝'
let obj = {
name: 'yujiabao',
say: function () {
console.log(this.name)
}
}
obj.say() // yujiabao
setTimeout(obj.say, 0) // 于家宝
但是我们实际上是需要this指向obj,所以要去修改this指向。
setTimeout(obj.say.bind(obj), 0) // yujiabao
call、apply、bind的使用
1. call
func.call(thisArg,arg1,arg2...)
作用:修改函数的this指向和向函数传递参数,并直接调用该函数,相当于调用这个函数的时候临时修改一下this指向。
参数:
- thisArg:func函数的this指向。
- arg1,arg2...:向函数传递的参数,是一个一个单独传递的。
返回值:函数的返回值。
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. apply
类似于call(),只是传递参数的方式不一样。
func.call(thisArg,argsArray)
作用:修改函数的this指向和向函数传递参数,并直接调用该函数,相当于调用这个函数的时候临时修改一下this指向。
参数:
- thisArg:func函数的this指向。
- argsArray:向函数传递的参数,是传递一个数组的集合。
返回值:函数的返回值。
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岁
3. bind
func.bind(thisArg,arg1,arg2...)
作用:修改函数的this指向和向函数传递参数,不会直接调用该函数,而是返回一个新的函数,这就是创建一个永久修改this指向的函数。
参数:
- thisArg:创建的新函数的this指向。
- arg1,arg2...:向函数传递的参数,是一个一个单独传递的。
返回值:修改了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岁
call、apply、bind的区别
1. 传参区别
1.1 call
call()方法传参是一个一个单独传参。
obj.say.call(this, 10, 8)
1.2 apply
apply()方法传参是传入一个数组,该数组是参数的集合。
obj.say.apply(this, [10, 8])
1.3 bind
bind()方法传参也是一个一个单独的传参,但是可以分批传参。
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岁
2.调用区别
2.1 call和apply
call()和apply()方法修改完函数的this指向后会立即调用该函数,相当于调用函数的时候临时修改一下this指向。
// 通过 apply 立即执行
obj.say.apply(this, [10, 8])
// 通过 call 立即执行
obj.say.call(this, 10, 8)
2.2 bind
bind()方法是返回一个修改了this指向的新的函数,通过调用这个函数去实现修改this指向的效果。
let newSay = obj.say.bind(this, 10)
newSay(8) // 于家宝今年18岁
call、apply、bind的相同点
都运用于修改函数的this指向,如果不传入第一个参数或者传入undefined、null,this指向为全局对象。
应用场景
1. call和apply
apply()和call()都是临时修改函数的this指向,当一个对象没有某个方法,而其他对象有这个方法,就可以借助apply()或者call()去临时使用这个方法。
这里obj2()方法没有say(),在这里借用obj1()的。
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
上面的方法可以实现基本需求,但是当传入的参数数量不固定的时候就失效了,这个时候就可以考虑call()或者apply(),因为参数数量不固定,所以这里使用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
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))
注意事项
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()是无法生效的。
2. 连续使用多次call/apply
会报错,用完一次以后又不是返回个函数哪来的第二次调用。