先记住这个答案
普通函数的 this 不看定义位置,只看调用方式。拿到一个函数调用,按四条规则从高到低排查:第一,是不是 new 调用,是的话 this 就是新创建的对象;第二,有没有用 call 或 apply 或 bind 显式指定,有就用传入的那个值;第三,调用时点号左边有没有对象,有就是那个对象;第四,前面都不是,走默认绑定,严格模式下是 undefined,非严格模式下是全局对象。箭头函数根本没有自己的 this,它用的是定义时外层作用域的 this。
- 先看是不是
new,再看有没有显式绑定 - 再看调用时点号左边有没有对象
- 都不是就走默认绑定,严格模式是
undefined - 箭头函数沿定义位置的作用域链查找,不受调用方式影响
四种绑定用一段代码全部看清
同一个普通函数,换不同方式调用,this 就会不一样。
'use strict'
function show() { return this?.name ?? 'none' }
const first = { name: 'first', show }
const second = { name: 'second' }
console.log(first.show())
console.log(show.call(second))
const bound = show.bind(first)
console.log(bound.call(second))
console.log(show())查看输出与解释
first
second
first
nonefirst.show() 是隐式绑定,点号左边是 first,所以 this 指向它。show.call(second) 是显式绑定,手动把 this 指定为 second。bound 是用 bind 固定了 this 为 first 的包装函数,后面再用 call 传 second 也改不了,因为 bind 创建的绑定是永久的。最后一行 show() 前面什么都没有,走默认绑定,严格模式下 this 是 undefined,undefined?.name 返回 undefined,最终打印 none。
new 调用为什么优先级最高
new 会创建一个全新的对象,然后把 this 指向它,让构造函数往里面挂属性。即使函数已经被 bind 过,new 也会忽略 bind 绑定的 this,改用新对象。
new 覆盖 bindJavaScriptfunction Person(name) { this.name = name }
const BoundPerson = Person.bind({ name: 'ignored' })
const p = new BoundPerson('actual')
console.log(p.name)查看输出与解释
actualBoundPerson 已经用 bind 把 this 绑到了 { name: 'ignored' },但 new 调用时创建了新对象并让 this 指向它,所以 p.name 是 'actual'。不过 bind 预设的参数仍然有效,new 只覆盖 this,不影响参数。
这个优先级在实际项目里很少直接用到,但面试经常问,考的是你对 new 内部机制的理解。
隐式绑定最容易丢
隐式绑定的问题是它依赖调用时的写法。只要把方法从对象上取下来单独调用,this 就丢了。
比如 const fn = obj.show 之后再 fn(),这时候 fn 前面没有对象,走默认绑定,this 变成 undefined(严格模式)或全局对象。事件回调和 setTimeout 里传方法引用时最容易踩这个坑。
解决办法有三种:用 bind 固定 this,用箭头函数包一层(箭头函数会捕获外层的 this),或者在 class 里用箭头函数作为类字段属性。React class 组件时代经常看到构造函数里写 this.handleClick = this.handleClick.bind(this),就是在解决这个问题。
非严格模式的陷阱
非严格模式下,默认绑定的 this 不是 undefined 而是全局对象(浏览器里是 window,Node.js 里是 global)。更隐蔽的是,call(null) 和 call(undefined) 也会被替换成全局对象,call(42) 会被装箱成 Number 对象。
现代代码基本都是严格模式。ES 模块天然严格,class 内部也天然严格。面试时最好主动说明自己是在严格模式下分析的,避免因为模式不同导致答案和面试官预期不一致。
容易答错的地方
- 以为
this看定义位置 - 普通函数的
this和它写在哪里没有关系。同一个函数可以挂到多个对象上,每次调用的this都可能不同。只有箭头函数才是看定义位置。 - 把箭头函数放进四条规则里讨论
- 箭头函数没有自己的
this,call和bind以及new对它都无效。它用的是定义时外层作用域的this,这个值在定义那一刻就固定了,后续怎么调用都不会变。面试时应该明确说「箭头函数不参与这套规则」。
面试官还会怎么问?
new 和 bind 同时出现,谁赢?
new 赢。new 创建新对象并绑定为 this,bind 预设的 this 被忽略。但 bind 预设的参数仍然生效,它们会排在 new 调用时传入的参数前面。
(0, obj.fn)() 的 this 是什么?
逗号表达式只保留最后一个值,也就是函数引用本身,点号左边的对象信息丢失了。这时候相当于普通调用,走默认绑定,严格模式下 this 是 undefined。Webpack 打包 CommonJS 模块时经常生成这种写法来避免模块函数里的 this 指向 exports。
回调函数里的 this 为什么经常丢?
把 obj.fn 作为回调传出去,实际执行时调用者是 setTimeout 或事件系统,不会用 obj.fn() 这种写法来调,隐式绑定就丢了。解决方案是用 bind 固定、箭头函数包裹,或者在 class 里用箭头函数作为类字段。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。