先记住这个答案
三个方法都能指定函数执行时的 this。call 和 apply 是当场调用函数,区别只是参数写法:call(obj, a, b) 一个个传,apply(obj, [a, b]) 放进数组传。bind(obj, a) 不会调用函数,而是返回一个新函数,这个新函数的 this 永远是 obj,第一个参数永远是 a,调用时再传的参数排在后面。所以需要「现在就调」用 call 或 apply,需要「把函数交给别人以后再调」用 bind。
call和apply立即执行,bind返回新函数call逐个传参,apply传数组bind之后this固定,再bind或call都改不了- 用
new调用绑定函数时,this换成新实例
三种写法放在一起比
同一个函数,分别用三种方式调用,再给 bind 预置一个参数。
function describe(prefix, suffix) {
return prefix + this.name + suffix
}
const user = { name: 'Ada' }
console.log(describe.call(user, '[', ']')) // 逐个传参,立即执行
console.log(describe.apply(user, ['<', '>'])) // 数组传参,立即执行
const greet = describe.bind(user, 'Hi ') // 不执行,返回新函数
console.log(greet('!')) // 预置的 'Hi ' 在前,'!' 在后
console.log(greet.call({ name: 'Bob' }, '?')) // this 已固定,改不了查看输出与解释
[Ada]
<Ada>
Hi Ada!
Hi Ada?前两行在写下的那一刻就执行了。greet 是一个新函数,调用它时,bind 时给的 'Hi ' 排在第一个,调用时传的 '!' 排在后面。最后一行用 call 传了 Bob,但 this 早被 bind 固定成 user,所以还是 Ada。
apply 以前常用来把数组展开成参数,比如 Math.max.apply(null, arr)。有了展开语法以后,直接写 Math.max(...arr) 更清楚,apply 的出场机会少了很多。
bind 返回的函数有哪些特点
bind 返回的叫绑定函数,它内部记住了三样东西:原函数、固定的 this、预置的参数。调用它时,就用固定的 this 加上「预置参数 + 本次参数」去调原函数。
- 再
bind一次也改不了this fn.bind(a).bind(b)调用时,this还是a。第二次bind包的是第一个绑定函数,而第一个绑定函数调用原函数时,永远用a,外面传什么this都不看。不过第二次bind预置的参数仍然有效,会接在第一次的参数后面。- 用
new调用时this会变 - 绑定函数被
new调用时,bind固定的this会被忽略,this换成新创建的实例,预置参数照样生效。这是new的优先级高于bind的体现,也是手写bind时最容易漏掉的一点。另外,箭头函数本身没有this,对它bind只有预置参数有用。 length和name也会变- 绑定函数的
length等于原函数的length减去预置参数个数,最小是 0。name会变成bound describe这样带bound前缀的名字,调试时看调用栈能认出来。
容易答错的地方
- 解绑事件时又
bind了一次 el.addEventListener('click', this.onClick.bind(this))之后,写el.removeEventListener('click', this.onClick.bind(this))解不掉。每次bind返回的都是新函数,两个引用不相等。正确做法是先把this.onClick.bind(this)存到一个变量里,添加和移除都用它。- 以为
bind的优先级最高 - 普通调用、
call、再bind都改不了绑定函数的this,但new可以。只背「bind优先级最高」会在追问new时答错。 - 以为
apply的第二个参数必须是数组 - 它接受任何类数组对象,比如
arguments、{ length: 2, 0: 'a', 1: 'b' }都行。传null或undefined表示不传参数。
面试官还会怎么问?
手写一个 bind 要注意什么?
核心是返回一个函数,里面用 apply 调原函数,参数是预置参数拼上调用时的参数。进阶要处理 new 的情况:判断 this instanceof 返回的函数,是的话用新实例当 this,并且让返回函数的 prototype 继承原函数的 prototype。
call 的第一个参数传 null 会怎样?
非严格模式下,this 会变成全局对象;严格模式下,this 就是 null。所以只想传参不关心 this 时传 null,要确认函数里没有用到 this。
性能上 call 和 apply 有区别吗?
apply 理论上多一步把类数组拆成参数列表,但现代引擎里这点差距可以忽略,按参数的形态选就行:手里已经是数组就用 apply 或展开语法,参数零散就用 call。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。