每当提到 JavaScript 的 this
关键字,许多开发者都会感到头痛。它似乎总是以出乎意料的方式工作,让人捉摸不透。但事实上,一旦你理解了 this
的行为,一切都会变得清晰起来。
this
的基本概念
this
在 JavaScript 中是一个特殊的关键字,它代表的是函数执行上下文中的“当前对象”。然而,this
的指向并不是固定的,它取决于函数是如何被调用的。这一点在 ECMAScript 规范中有详细的定义:tc39.es/ecma262/#se…
并且,如果你不了解它的运作机制,有时就会出现一些你认为的莫名其妙的现象。
那么,这个 this
到底说了什么内容呢?
说起来也简单,this
的指向会根据函数的调用方式不同而变化。 对于一些常见的函数调用模式,我们可以总结如下:
-
全局作用域中的
this
- 当在全局作用域中调用一个函数时,
this
通常指向全局对象(在浏览器中是window
,在 Node.js 中是global
)。
- 当在全局作用域中调用一个函数时,
-
作为普通函数调用时的
this
- 当一个函数被直接调用时(不是作为一个对象的方法),
this
通常指向全局对象(非严格模式下)或者undefined
(严格模式下)。
- 当一个函数被直接调用时(不是作为一个对象的方法),
-
作为对象方法调用时的
this
- 当一个函数作为对象的一个方法被调用时,
this
通常指向那个对象。
- 当一个函数作为对象的一个方法被调用时,
-
构造函数中的
this
- 使用
new
关键字调用构造函数时,this
指向新创建的对象实例。
- 使用
-
事件处理程序中的
this
- 当一个函数作为事件处理程序被调用时,
this
通常指向触发事件的元素。
- 当一个函数作为事件处理程序被调用时,
-
箭头函数中的
this
- 箭头函数不会绑定自己的
this
,而是继承自外围函数的作用域中的this
。
- 箭头函数不会绑定自己的
-
使用
.call
,.apply
,.bind
改变this
- 可以使用这些方法显式地设置函数执行时的
this
值。
- 可以使用这些方法显式地设置函数执行时的
为了描述this 的不同绑定方式,程序员们达成了共识,给this的绑定也分为了一下几类
社区里广泛使用的术语
默认绑定
当函数独立调用时,this指向全局对象(在浏览器环境中为window对象)。
示例:
1function sayName() {
2 console.log(this.name);
3}
4
5var name = '全局名称';
6sayName();
注意:当这里是严格模式的时候,会访问undefined导致TypeError
隐式绑定
当函数作为对象的方法调用时,this指向该对象。
1var person = {
2 name: '张三',
3 sayName: function() {
4 console.log(this.name);
5 }
6};
7
8person.sayName();
函数调用中的this
当一个函数作为普通函数调用时,this
的行为取决于上下文:
- 非严格模式下,
this
指向全局对象。 - 严格模式下,
this
为undefined
。
1function greet() {
2 console.log(this);
3}
4
5greet();
箭头函数中的this
箭头函数不绑定自己的this
。它们继承外部函数或全局作用域中的this
。
1const person = {
2 name: 'Alice',
3 greet: () => {
4 console.log('Hello, ' + this.name);
5 }
6};
7
8person.greet();
new绑定this
在构造函数中,this
指向新创建的对象实例。
1function Person(name) {
2 this.name = name;
3}
4
5const alice = new Person('Alice');
6console.log(alice.name);
显式绑定
通过call、apply和bind方法,可以显式指定函数的this指向。
示例:
1function sayName(age) {
2 console.log(this.name + ',年龄:' + age);
3}
4
5var person = {
6 name: '李四'
7};
8
9sayName.call(person, 25);
案例
来吧,让我们从最简单的案例开始看。
1function sayHello() {
2 console.log(this);
3}
4
5sayHello();
请仔细阅读上面的代码,然后你认为 sayHello
函数中的 this
是什么?
要回答这个问题,我们先要了解全局作用域的this
在全局作用域(非严格模式下),this
指向全局对象。在浏览器环境中,这通常指的是window
对象。
1console.log(this);
在严格模式下(use strict
), 全局作用域中的this
将被设置为undefined
。
1'use strict';
2console.log(this);
很明显, 这里是普通函数的调用,且不在严格模式下,所以this指向全局。我们在浏览器环境中运行得到的答案就是window,在node环境指向的是globalThis
接下来我们来看一个稍微复杂一点的例子:
1const obj = {
2 name: 'Alice',
3 greet: function() {
4 console.log(`Hello, my name is ${this.name}`);
5 }
6};
7
8obj.greet();
请仔细阅读上面的代码,然后你认为 greet
函数中的 this
是什么?
相信你能够很自信的回答这个问题,greet
函数中的 this
指向 obj
对象。
这个答案是正确的,但如果我追问你是怎么得到这个答案的,我猜不了解 this
行为的你可能会说,因为它是作为一个对象的方法被调用的,所以 this
指向 obj
。
这里的情况是函数作为对象的方法被调用,因此遵循第3条规则。所以答案是’Hello, my name is Alice’
接下来我们继续增加复杂度:
1const obj = {
2 name: 'Alice',
3 greet: function() {
4 console.log(`Hello, my name is ${this.name}`);
5 }
6};
7
8const greet = obj.greet;
9greet();
这里我们将 greet
函数赋值给了一个新的变量,并直接调用它。你认为 greet
函数中的 this
是什么? 它依然是作为一个普通函数被调用了,遵循第二条,所以如果在浏览器环境下打印的仍是windows,但在node环境中会是globalThis
我们再来看看使用 new
关键字调用构造函数的情况:
1function Person(name) {
2 this.name = name;
3 this.sayHello = function() {
4 console.log(`Hello, my name is ${this.name}`);
5 };
6}
7
8const alice = new Person('Alice');
9alice.sayHello();
这里我们使用 new
关键字创建了一个新的 Person
实例。你认为 sayHello
函数中的 this
是什么? sayHello
函数中的 this
指向 alice
对象。
这里的情况是函数作为对象的方法被调用,因此遵循第3条规则。此外,alice
对象是在构造函数中创建的,因此遵循第4条规则。
接下来我们看看箭头函数中的 this
指向:
1const obj = {
2 name: 'Alice',
3 greet: () => {
4 console.log(`Hello, my name is ${this.name}`);
5 }
6};
7
8obj.greet();
这里我们使用了箭头函数。你认为 greet
函数中的 this
是什么?
greet
函数中的 this
指向全局对象(非严格模式下)或者 undefined
(严格模式下)。
这里的情况是箭头函数,因此遵循第6条规则,它继承了外围函数的作用域中的 this
。
我们再看一个例子
1const user = {
2 name: 'Alice',
3 logName: function() {
4 console.log('logName:', this.name);
5
6 const innerFunc1 = () => {
7 console.log('innerFunc1:', this.name);
8 };
9
10 const innerFunc2 = () => {
11 console.log('innerFunc2:', this.name);
12
13 const innerFunc3 = () => {
14 console.log('innerFunc3:', this.name);
15
16 setTimeout(() => {
17 console.log('setTimeout:', this.name);
18 }, 1000);
19 };
20
21 innerFunc3();
22 };
23
24 innerFunc1();
25 innerFunc2();
26 }
27};
28
29
30user.logName();
31
32
33const globalArrowFunc = () => {
34 console.log('globalArrowFunc:', this);
35};
36
37globalArrowFunc();
先别看下面,你能独立分析出这里所有日志的内容吗?
- 分析
-
logName: 在对象方法
logName
内部,this
指向user
对象,因此this.name
输出Alice
。 -
innerFunc1: 箭头函数
innerFunc1
继承了logName
的this
,因此this.name
输出Alice
。 -
innerFunc2: 同样,箭头函数
innerFunc2
继承了logName
的this
,因此this.name
输出Alice
。 -
innerFunc3: 箭头函数
innerFunc3
也继承了logName
的this
,因此this.name
输出Alice
。 -
setTimeout: 即使在
setTimeout
中定义了一个箭头函数,它依然继承了logName
的this
,因此this.name
输出Alice
。 -
globalArrowFunc: 在全局作用域中定义的箭头函数,
this
指向全局对象,因此this
输出全局对象。
你都分析对了吗?
this指向的优先级
- 当多种绑定规则同时存在时,它们的优先级顺序为:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。