(一)apply、call与bind都是函数对象的一个方法,主要作用都是改变函数体内部this的指向。
下面用一个例子来说明三者的作用:
function animals() {}
animals.prototype = {
name: 'dog',
say: function() {
console.log(`My name is ${this.name}`)
}
}
let dog = new animals
dog.say() //输出:My name is dog
//定义一个cat对象,通过apply或call来调用animals的say方法
const cat = { name: 'cat' }
dog.say.apply(cat) //输出:My name is cat
dog.say.call(cat) //输出:My name is cat
dog.say.bind(cat) //输出: say()
从上面的例子可以看出,call和apply改变了dog对象内部this的指向,指向了传入的cat对象
bind与apply、call的主要区别是:①bind返回的是整个函数的方法、而apply、call返回的是方法调用后返回的结果;②bind方法并非立即执行;apply和call方法是立即执行函数
下面一一对这三者做详细的区分:
(二)apply与call的区别:
(1)apply传入两个参数:①作为函数上下文的对象;②作为函数参数所组成的数组
const obj = { name: 'CCG' }
connectName(firstName, lastName){
console.log(`${firstName} ${this.name} ${lastName}`)
}
connectName.apply(obj, ['A', 'B']) //输出:A CCG B
上面的例子可以看出obj是connectName上下文的对象,connectName的this指向了obj这个对象;参数数组中的A和B传入了函数,分别对应firstName和lastName这两个参数。
(2)call传入两个参数:①函数上下文的对象;②一个参数列表,并非数组
const obj = { name: 'CCG' }
connectName(firstName, lastName) {
console.log(`${firstName} ${this.name} ${lastName}`)
}
connectName.call(obj, 'A', 'B') //输出:A CCG B
对比 apply 我们可以看到区别,A和 B 是作为单独的参数传给 connectName 函数,而不是放到数组中。
面对选择两者之一的时候不要太过纠结看个人需求:当你的参数是明确知道数量时用 call ,而不确定的时候用 apply,然后把参数 push 进数组传递进去。当参数数量不确定时,函数内部也可以通过 arguments 这个伪数组来遍历所有的参数。
(3)常见应用:
①数组之间的追加:平时我们会使用concat连接两个数组,但是返回了一个新数组;若想要在原来的数组本身进行追加,那么apply可以满足。
let arr1 = [1, 23, 'aa']
let arr2 = ['CCG', 'TAOTAO']
Array.prototype.push.apply(arr1, arr2) //arr1数组的值为:[1, 23, 'aa', 'CCG', 'TAOTAO'](ES5的写法)
arr1.push(...arr2) //arr1的数组的值:[1, 23, 'aa', 'CCG', 'TAOTAO'](ES6的写法:运用扩展运算符)
②获取数组中的最大值和最小值
const numArr = [1, 10, 15, 50]
const maxNum = Math.max.apply(null, numArr) //输出:50(ES5的写法)
const minNum = MAth.min.apply(null, numArr) //输出:1(ES5的写法)
const max = Math.max(...numArr) //输出:50(ES6的写法)
const min = Math.min(...numArr) //输出:1(ES6的写法)
apply与call的深刻运用:
题目:定义一个log方法,代理console.log方法(支持一个或者多个参数)。
解决方法如下:使用apply重写console.log方法,可以解决当log方法传入多个参数时能全部打印出来。
function log() {
console.log.apply(null, arguments)
}
log(1) //1
log(1, 3, 4) //1 3 4
如果在上面的基础上要求每个log消息添加一个(app)前缀。
这时候要注意log函数内部的arguments对象是个伪数组(类似数组),需要将其转为标准数组,再使用数组的unshift方法完成上面的要求。
function log() {
let args = Array.prototype.slice.call(arguments) //(ES5的写法,也可以写成=> [].slice.call(arguments))
let args = Array.from(arguments) //(ES6的写法)
args.unshift('(app)')
console.log.apply(null, args)
}
log('hello word', 'my name is JJ')//(app)hello word (app)my name is JJ
(三)bind的详解
(1)MDN的解释是:bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
const log = (){
console.log(this.x)
}
const obj = {
x: 3
}
log() //undefined
const func = log.bind(obj)
func() //3
从上面的函数可以看出:这里创建了一个新的函数 func,当使用 bind() 创建一个绑定函数之后,它被执行的时候,它的 this 会被设置成 obj , 而不是像我们调用 log() 时的全局作用域。
注意:如果连续bind()两次或者以上,是无效的只有第一次bind()才生效。原因是 bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。
(四)总结一下:
(1)三者都是用来改变函数的this对象的指向
(2)三者的第一个参数都是this要指向的对象,即上下文
(3)apply与call都是立即调用,bind是返回对应函数,稍后调用