JavaScript 中的 `this`

138 阅读4分钟

JavaScript 中的 this

this 是 JavaScript 中一个非常重要但容易混淆的关键字。它的值并不是在定义时确定的,而是在函数调用时动态绑定的,也就是我们常说的上下文。


1. 什么是 this?

this 是一个上下文相关的关键字,用于引用当前执行上下文中的对象。其具体值取决于函数的调用方式。this 是在运行时绑定的,而不是在编写时绑定的。它的指向取决于函数是如何被调用的,而不是函数是如何定义的。简单来说,this 总是指向调用该函数的对象。


2. this 的绑定规则

JavaScript 中 this 的绑定规则主要包括以下几种情况:

2.1 默认绑定

在全局执行上下文中(在任何函数体外部),this 始终指向全局对象。在浏览器中,全局对象是 window;在 Node.js 环境中,全局对象是 global

function foo() {
  console.log(this);
}
foo(); // 在浏览器中,输出 window

严格模式下,thisundefined

'use strict';
function foo() {
  console.log(this);
}
foo(); // undefined
2.2 隐式绑定

当函数作为对象的方法调用时,this 指向调用该方法的对象。

const obj = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  }
};
obj.greet(); // 输出 "Alice"

注意:如果将方法赋值给一个变量,隐式绑定会丢失,this 会回退到默认绑定规则。

const greet = obj.greet;
greet(); // 在非严格模式下,输出 undefined 或 "window" 的属性
2.3 显式绑定

可以使用 callapplybind 方法显式指定 this 的值。

  • call 方法:调用函数并指定 this,参数以逗号分隔。
function greet(greeting, punctuation) {
  console.log(greeting + ' ' + this.name + punctuation);
}
const obj = { name: 'Alice' };
greet.call(obj, 'Hello', '!'); // 输出 "Hello Alice!"
  • apply 方法:与 call 类似,但参数以数组形式传入。
greet.apply(obj, ['Hi', '.']); // 输出 "Hi Alice."
  • bind 方法:返回一个绑定了指定 this 的新函数。
const boundGreet = greet.bind(obj, 'Hey');
boundGreet('!'); // 输出 "Hey Alice!"
2.4 new 绑定

使用 new 关键字调用函数时,该函数会作为构造函数执行。此时,this 指向新创建的对象实例。

function Person(name) {
  this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 输出 "Alice"
2.5 箭头函数绑定

箭头函数没有自己的 this,它会继承父作用域的 this。如果在全局作用域中定义箭头函数,那么它的 this 指向全局对象;如果在其他函数内部定义箭头函数,那么它的 this 指向包含它的函数(非箭头函数)的 this

const obj = {
  name: 'Alice',
  greet: () => {
    console.log(this.name);
  }
};
obj.greet(); // undefined,因为 this 指向定义时的外层作用域(通常是全局对象)

3. 特殊场景下的 this

3.1 在回调函数中

回调函数中的 this 指向取决于回调函数是如何被调用的。如果回调函数是作为对象的方法传递的,那么 this 可能不会像你期望的那样指向该对象。通常需要使用 bind、箭头函数或将 this 保存在一个变量中来解决这个问题。

const obj = {
  name: 'Alice',
  greet() {
    setTimeout(function() {
      console.log(this.name);
    }, 1000);
  }
};
obj.greet(); // undefined(普通函数的默认绑定)

// 使用箭头函数解决
const obj2 = {
  name: 'Alice',
  greet() {
    setTimeout(() => {
      console.log(this.name);
    }, 1000);
  }
};
obj2.greet(); // 输出 "Alice"
3.2 在事件处理程序中

在 DOM 事件处理程序中,this 默认指向触发事件的元素。

<button id="myButton">Click me</button>
  <script>
    const button = document.getElementById('myButton');
    button.addEventListener('click', function() {
      console.log(this === button); // 输出 true
    });
  </script>

如果使用箭头函数,this 将指向定义时的作用域。

document.getElementById('btn').addEventListener('click', () => {
  console.log(this); // 通常是全局对象
});
3.3 在类中

在类方法中,this 默认指向实例对象,但需要注意丢失绑定的情况。

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(this.name);
  }
}
const alice = new Person('Alice');
alice.greet(); // 输出 "Alice"

const greet = alice.greet;
greet(); // undefined,因为丢失了绑定

使用 bind 或箭头函数可以避免丢失绑定。


4. 常见面试题

以下是一些与 this 相关的常见面试题及解析:

4.1 全局作用域中的 this
console.log(this);

在浏览器中,输出什么?

解析:输出 window,因为在全局作用域中,this 指向全局对象。

4.2 函数调用中的 this
function foo() {
  console.log(this);
}
foo();

在非严格模式下和严格模式下分别输出什么?

解析:

  • 非严格模式下,输出 window
  • 严格模式下,输出 undefined
4.3 对象方法中的 this
const obj = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  }
};
obj.greet();

输出是什么?

解析:输出 Alice,因为 this 指向调用方法的对象 obj

4.4 this 丢失问题
const obj = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  }
};
const greet = obj.greet;
greet();

输出是什么?

解析:输出 undefined,因为方法赋值给变量后,this 指向全局对象(或 undefined 在严格模式下)。

4.5 箭头函数中的 this
const obj = {
  name: 'Alice',
  greet: () => {
    console.log(this.name);
  }
};
obj.greet();

输出是什么?

解析:输出 undefined,因为箭头函数没有自己的 this,它继承自定义时的外层作用域(通常是全局对象)。

4.6 构造函数中的 this
function Person(name) {
  this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name);

输出是什么?

解析:输出 Alice,因为 this 被绑定到新创建的实例对象。

4.7 callapply 的使用
function greet(greeting) {
  console.log(greeting + ' ' + this.name);
}
const obj = { name: 'Alice' };
greet.call(obj, 'Hello');
greet.apply(obj, ['Hi']);

输出是什么?

解析:

  • 输出 Hello Alice,因为 call 显式绑定了 this
  • 输出 Hi Alice,因为 apply 的作用与 call 类似。
4.8 bind 的使用
const obj = { name: 'Alice' };
function greet() {
  console.log(this.name);
}
const boundGreet = greet.bind(obj);
boundGreet();

输出是什么?

解析:输出 Alice,因为 bind 返回了一个绑定了 this 的新函数。