直接回答:this 的指向由函数被调用时的位置决定,和函数定义在哪里没关系。同一段函数体,挂在对象上调用就指向那个对象,单独拎出来调用就指向全局对象或 undefined(严格模式);想固定下来用 bind,而箭头函数根本不用管这件事——它直接沿用外层作用域的 this。
一个让人困惑的例子
1 | const obj = { |
两个调用的函数体一模一样,结果却不同。原因很简单:this 不是函数自带的东西,而是调用时被临时塞进来的。
可以把对象方法想成"函数存在某个地址上,对象保存了这个地址"。obj.foo 只是把地址复制给了 foo 这个变量,foo() 的调用位置上没有任何对象,所以 this 自然不是 obj。
四种调用形态,四种结果
| 调用形式 | this 指向 |
|---|---|
foo() 独立调用 |
非严格模式是全局对象,严格模式是 undefined |
obj.foo() 方法调用 |
obj |
foo.call(x) / foo.apply(x) |
x |
new Foo() 构造调用 |
新建出来的那个实例 |
记住一句话就够用了:看函数名左边有没有东西,有就是它,没有就是全局或 undefined。
改变指向的三个方法
1 | foo.call(obj, 1, 2); // 参数逐个传 |
三者的区别只在于"立刻执行还是返回新函数"和"参数怎么传"。实战里 bind 最常用,因为回调场景需要的是一个可以稍后执行、但 this 已经定好的函数。
注意:bind 绑定的 this 不会被后续的 call 覆盖,但被 new 调用时 this 会重新指向新实例——这是少数例外。
回调里为什么会丢 this
1 | setTimeout(obj.foo, 0); // this 丢了 |
把方法当回调传出去,等于只是把函数地址交了出去,调用点变成了定时器内部,this 自然就不对了。React 类组件里"方法里拿不到 this.props"就是同一个原因,解决办法也是这两个:构造函数里 bind,或者用箭头函数定义方法。
箭头函数:不要试图改变它
箭头函数没有自己的 this,它用的是定义时外层作用域的 this,而且 call、apply、bind 都改不了。
1 | const obj = { |
上面这段里 this 是外层(模块作用域或全局)的 this,不是 obj。所以对象方法不要写成箭头函数,而回调、map、then 这类需要沿用外层 this 的地方,箭头函数刚好合适。
顺带一提,箭头函数同样没有自己的 arguments 和 super,需要这两个的时候也不能用。
几种容易判断错的写法
1 | const obj = { |
this 只看紧挨着点号的最后一个对象,和它在对象里嵌套了多深无关。
1 | const foo = obj.b.foo; |
链式调用被拆开,this 也就随之消失。
实用结论
- 只想记住一条:this 由调用点决定,不由定义点决定;
- 需要固定
this时优先用箭头函数定义外层函数,或者显式bind; - 对象方法用简写或普通函数,回调用箭头函数,这个默认选择能避开大部分坑;
- 类组件里的事件处理函数记得
bind,或者改用函数组件和 Hooks——Hooks 里压根没有this这事。
本文整理自我自己早先的 JS 笔记,参照了阮一峰《JavaScript 的 this 原理》的思路,用自己的例子重新写了一遍,文字由 AI 协助改写后经我复核。
这篇笔记整理自我自己的实践记录,如果做法有出入,或者你踩过别的坑,欢迎到留言板一起聊聊。