Menu

JavaScript this 关键字:绑定规则与常见陷阱

this 在 JavaScript 中到底如何工作:四条绑定规则,箭头函数为什么不一样,以及如何避开经典的“this 是 undefined”陷阱。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

this 在调用时才确定

关于 this 的大部分困惑,都来自一个错误的假设:以为 this 取决于函数在哪里定义。并不是。this 取决于函数如何被调用。

来看同一个函数的两种调用方式:

同一个函数,this 却不同。第一次调用时点号左边是 user,所以 this 是 user。第二次调用时左边什么都没有,所以 this 是 undefined。函数本身没变,变的是调用位置。

记住这个思路:要弄清 this,看调用,而不是看定义。

四条绑定规则

this 的设置方式有四种。你遇到的几乎每个和 this 有关的问题,只要判断出适用哪一条,就能找到答案。

1. 方法调用:obj.fn()

函数作为对象的属性被调用时,this 就是那个对象:

点号左边的东西就成了 this,就这么简单。

2. 普通调用:fn()

前面没有对象时,严格模式下 this 是 undefined(模块和类会自动开启严格模式),非严格模式下是全局对象:

“this 是 undefined”的报错就是从这里来的。把方法从对象上取下来,方法调用就变成了普通调用:

调用前没有 counter.,就没有绑定。函数不会记得它原来属于哪个对象。

3. 显式绑定:.call()、.apply()、.bind()

你可以把 this 强制设为任何你想要的值:

.call 和 .apply 会立即调用函数,区别只在于参数的传递方式。.bind 返回一个新函数,它的 this 被永久锁定,这在回调函数中很有用。

4. new 调用:new Fn()

用 new 调用函数会创建一个新对象,并把它绑定到 this:

类在底层用的就是这个机制。我们会在后面的章节中介绍类。

箭头函数没有自己的 this

箭头函数有意打破了上面的规则。它根本不绑定 this,而是从外层作用域继承,在箭头函数定义的那一刻就固定下来:

位于模块顶层的箭头函数捕获的是模块的 this,也就是 undefined。即使以 user.arrow() 的形式调用,箭头函数也不会重新绑定。

这听起来像 bug,但这恰恰是它的设计目的。箭头函数在方法内部最能发挥作用,因为那里你通常想保留外层的 this:

setInterval 里的箭头函数从 start 继承 this,而 start 是以 timer.start() 的形式调用的,所以 this.seconds 能正常工作。如果这里用普通的 function,它会有自己的 this(setInterval 传进来的值),代码就出错了。

经验法则:方法内部的回调函数用箭头函数,方法本身用普通函数。

经典的回调函数陷阱

这是 this 出问题最常见的方式。你把一个方法当作回调函数传出去,它就丢失了绑定:

setTimeout 以普通调用的方式调用这个函数,而不是 c.increment()。有三种修复方法:

三种方法都可以。用箭头函数包一层通常最清晰。

顶层的 this

顶层的 this 取决于代码在哪里运行:

  • 浏览器脚本(非模块): this 是 window。
  • ES 模块(包括大多数现代打包后的代码):this 是 undefined。
  • Node.js CommonJS 模块: this 是 module.exports。

要在各种环境中可靠地引用全局对象,请使用 globalThis:

实际开发中不应该依赖顶层 this。确实需要全局对象时用 globalThis,其他情况下显式地传递值。

快速判断流程

看到 this 又不确定它是什么时,按下面的顺序逐条判断:

  1. 函数是箭头函数吗?那么 this 就是外层作用域中的值,完全不用管调用位置。
  2. 是用 new 调用的吗?那么 this 是新创建的对象。
  3. 是用 .call、.apply 或绑定过的函数调用的吗?那么 this 是传进去的值。
  4. 是以 obj.method() 的形式调用的吗?那么 this 是 obj。
  5. 是以普通的 fn() 形式调用的吗?那么严格模式下 this 是 undefined。

按这个顺序走一遍,任何情况都能判断出来。

下一节:高阶函数

既然 this 不再神秘,你就可以学习让 JavaScript 如此富有表现力的模式了:把函数当作值传来传去。接下来我们看看高阶函数,也就是接收或返回其他函数的函数,以及它们如何支撑起数组方法、事件处理函数和大多数真实的 JavaScript 代码。

常见问题

JavaScript 中的 this 指向什么?

this 指向调用函数的那个对象,而不是函数定义的位置。在 user.greet() 中,this 是 user。在普通的 greet() 调用中,严格模式下 this 是 undefined(非严格模式下是全局对象)。规则看的是调用位置,而不是定义位置。

为什么函数里的 this 是 undefined?

你很可能把方法从对象上取了下来单独调用,或者把它当作回调函数传了出去。const fn = user.greet; fn(); 会丢失绑定,因为调用位置的点号左边没有对象。可以用 .bind(user)、用箭头函数包一层,或者以 user.greet() 的形式调用来解决。

箭头函数中的 this 有什么不同?

箭头函数没有自己的 this,它在定义时从外层作用域继承 this。这让它非常适合在方法内部作回调函数,因为你通常想保留外层的 this。这也意味着 .call()、.apply() 和 .bind() 都无法改变箭头函数的 this。

脚本顶层的 this 是什么?

在浏览器的普通脚本中,顶层 this 是 window 对象。在 ES 模块中,它是 undefined。在 Node.js 的 CommonJS 模块中,它是 module.exports。跨环境通用的写法是 globalThis,它始终指向全局对象。

Coddy 编程语言插图

和 Coddy 一起学编程

开始