在 JavaScript 中,this 是一个非常重要但也容易引起混淆的概念。特别是在普通函数和箭头函数之间,this 的行为有着显著的区别。在这篇文章中,我们将通过一个代码示例,深入理解 this 在不同场景下的指向。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = 1;

function fn1() {
console.log(this.a);
}

const fn2 = () => {
console.log(this.a);
}

const obj = {
a: 10,
fn1: fn1,
fn2: fn2
}

fn1(); // 输出: 1
fn2(); // 输出: 1
obj.fn1(); // 输出: 10
obj.fn2(); // 输出: 1

让我们逐行解析这段代码,弄清楚为什么会得到这样的输出。


全局变量 a

首先,我们定义了一个全局变量 a,并赋值为 1

1
var a = 1;

在浏览器环境中,使用 var 声明的全局变量会成为 window 对象的属性。所以此时,window.a 的值为 1


普通函数 fn1

接下来定义了一个普通函数 fn1

1
2
3
function fn1() {
console.log(this.a);
}

在 JavaScript 中,普通函数的 this 指向取决于它被调用的上下文。也就是说,谁调用了这个函数,this 就会指向谁。


箭头函数 fn2

然后,我们定义了一个箭头函数 fn2

1
2
3
const fn2 = () => {
console.log(this.a);
}

与普通函数不同,箭头函数不会绑定自己的 this。它会继承定义时所在的上下文的 this 值。因此,fn2this 始终是它在定义时的环境中的 this


定义对象 obj

我们定义了一个对象 obj,包含属性 a 和两个方法 fn1fn2

1
2
3
4
5
const obj = {
a: 10,
fn1: fn1,
fn2: fn2
}

这里的 a 是对象 obj 的属性,而 fn1fn2 分别引用前面定义的普通函数和箭头函数。


函数调用分析

1. 调用 fn1()

1
fn1();  // 输出: 1

这是直接调用 fn1。由于 fn1 是在全局作用域中调用的,因此它的 this 默认指向全局对象 window。于是 this.a 相当于 window.a,它的值为 1

2. 调用 fn2()

1
fn2();  // 输出: 1

fn2 是一个箭头函数。箭头函数的 this 是在定义时决定的。在这里,fn2 是在全局作用域中定义的,因此它的 this 也是指向全局对象 window,所以输出的 this.a 也是 1

3. 调用 obj.fn1()

1
obj.fn1();  // 输出: 10

这次调用 fn1 是通过对象 obj 来进行的。在这种情况下,this 指向调用函数的对象 obj,所以 this.a 实际上是 obj.a,即 10

4. 调用 obj.fn2()

1
obj.fn2();  // 输出: 1

虽然 fn2 是通过对象 obj 调用的,但由于 fn2 是箭头函数,它的 this 并不会被调用方式所影响。fn2this 是在定义时就已经决定了的,它指向全局对象 window,因此 this.a 仍然是 window.a,值为 1


小结

通过这个例子,我们可以清楚地看到 JavaScript 中 this 的不同表现:

  1. 普通函数this 的指向取决于它的调用方式。谁调用了这个函数,this 就指向谁。
  2. 箭头函数this 不会绑定调用时的对象,而是继承自定义时的上下文 this

执行结果总结:

  • fn1() 输出 1:普通函数,在全局作用域中调用,this 指向 window
  • fn2() 输出 1:箭头函数,继承全局 this,所以输出 window.a
  • obj.fn1() 输出 10:普通函数,通过对象调用,this 指向 obj
  • obj.fn2() 输出 1:箭头函数,依旧继承定义时的 this,输出 window.a

结论

在 JavaScript 中,理解 this 的指向对于编写和调试代码非常重要。普通函数的 this 根据调用方式而变化,而箭头函数的 this 是固定的。掌握了这些规则,可以帮助我们更好地编写和调试代码,避免 this 指向错误带来的困扰。