测验

解释 JavaScript 中 `this` 的工作原理

主题
JavaScriptOOP

TL;DR

this 没有简单的解释;它是 JavaScript 中最令人困惑的概念之一,因为它的行为与其他许多编程语言不同。this 关键字的单行解释是,它是一个对函数执行上下文的动态引用。

更长的解释是,this 遵循以下规则:

  1. 如果在调用函数时使用了 new 关键字,这意味着该函数被用作函数构造函数,则函数内部的 this 是新创建的对象实例。
  2. 如果在 class constructor 中使用了 this,则 constructor 内部的 this 是新创建的对象实例。
  3. 如果使用 apply()call()bind() 调用/创建函数,则函数内部的 this 是作为参数传入的对象。
  4. 如果一个函数被调用为一个方法(例如 obj.method())——this 是该函数所属的对象。
  5. 如果一个函数被调用为自由函数调用,这意味着它是在没有任何上述条件的情况下被调用的,this 是全局对象。在浏览器中,全局对象是 window 对象。如果在严格模式 ('use strict';) 中,this 将是 undefined 而不是全局对象。
  6. 如果应用了多个上述规则,则优先级较高的规则将获胜并设置 this 的值。
  7. 如果该函数是 ES2015 箭头函数,它会忽略上述所有规则,并在创建时接收其周围作用域的 this 值。

如需深入解释,请查看 Arnav Aggrawal 在 Medium 上的文章


this 关键字

在 JavaScript 中,this 是一个关键字,它引用函数或脚本的当前执行上下文。这是 JavaScript 中的一个基本概念,理解 this 的工作原理对于构建健壮且可维护的应用程序至关重要。

全局使用

在全局范围内,this 引用全局对象,在 Web 浏览器中是 window 对象,在 Node.js 环境中是 global 对象。

console.log(this); // 在浏览器中,这将记录 window 对象(对于非严格模式)。

在常规函数调用中

当一个函数在全局上下文中或作为独立函数被调用时,this 引用全局对象(在非严格模式下)或 undefined(在严格模式下)。

function showThis() {
console.log(this);
}
showThis(); // 在非严格模式下:Window(全局对象)。在严格模式下:undefined。

在方法调用中

当一个函数作为对象的方法被调用时,this 引用该方法被调用的对象。

const obj = {
name: 'John',
showThis: function () {
console.log(this);
},
};
obj.showThis(); // { name: 'John', showThis: ƒ }

请注意,如果你这样做,它就和常规函数调用一样,而不是方法调用。this 已经失去了它的上下文,不再指向 obj

const obj = {
name: 'John',
showThis: function () {
console.log(this);
},
};
const showThisStandalone = obj.showThis;
showThisStandalone(); // 在非严格模式下:Window(全局对象)。在严格模式下:undefined。

在函数构造器中

当一个函数被用作构造器(使用 new 关键字调用)时,this 指的是新创建的实例。在下面的例子中,this 指的是正在创建的 Person 对象,并且 name 属性是在该对象上设置的。

function Person(name) {
this.name = name;
}
const person = new Person('John');
console.log(person.name); // "John"

在类构造函数和方法中

在 ES2015 类中,this 的行为与在对象方法中一样。它指的是类的实例。

class Person {
constructor(name) {
this.name = name;
}
showThis() {
console.log(this);
}
}
const person = new Person('John');
person.showThis(); // Person {name: 'John'}
const showThisStandalone = person.showThis;
showThisStandalone(); // `undefined` because in JavaScript class bodies, all methods are strict mode by default, even if you don't add 'use strict'

显式绑定 this

你可以使用 bind()call()apply() 来显式设置函数的 this 的值。

使用 call()apply() 方法允许您在调用函数时显式设置 this 的值。

function showThis() {
console.log(this);
}
const obj = { name: 'John' };
showThis.call(obj); // { name: 'John' }
showThis.apply(obj); // { name: 'John' }

bind() 方法创建一个新函数,并将 this 绑定到指定的值。

function showThis() {
console.log(this);
}
const obj = { name: 'John' };
const boundFunc = showThis.bind(obj);
boundFunc(); // { name: 'John' }

在箭头函数中

箭头函数没有自己的 this 上下文。相反,this 是词法作用域的,这意味着它继承了定义时周围作用域的 this 值。

在这个例子中,this 指的是全局对象(window 或 global),因为箭头函数没有绑定到 person 对象。

const person = {
firstName: 'John',
sayHello: () => {
console.log(`Hello, my name is ${this.firstName}!`);
},
};
person.sayHello(); // "Hello, my name is undefined!"

在下面的例子中,箭头函数中的 this 将是其封闭上下文的 this 值,因此它取决于 showThis() 的调用方式。

const obj = {
name: 'John',
showThis: function () {
const arrowFunc = () => {
console.log(this);
};
arrowFunc();
},
};
obj.showThis(); // { name: 'John', showThis: ƒ }
const showThisStandalone = obj.showThis;
showThisStandalone(); // 在非严格模式下:Window(全局对象)。在严格模式下:undefined。

因此,箭头函数中的 this 值不能通过 bind()apply()call() 方法设置,它也不会指向对象方法中的当前对象。

const obj = {
name: 'Alice',
regularFunction: function () {
console.log('Regular function:', this.name);
},
arrowFunction: () => {
console.log('Arrow function:', this.name);
},
};
const anotherObj = {
name: 'Bob',
};
// 使用 call/apply/bind 和常规函数
obj.regularFunction.call(anotherObj); // Regular function: Bob
obj.regularFunction.apply(anotherObj); // Regular function: Bob
const boundRegularFunction = obj.regularFunction.bind(anotherObj);
boundRegularFunction(); // Regular function: Bob
// 使用 call/apply/bind 和箭头函数,`this` 指的是全局作用域,并且不能被修改。
obj.arrowFunction.call(anotherObj); // Arrow function: window/undefined (depending if strict mode)
obj.arrowFunction.apply(anotherObj); // Arrow function: window/undefined (depending if strict mode)
const boundArrowFunction = obj.arrowFunction.bind(anotherObj);
boundArrowFunction(); // Arrow function: window/undefined (depending if strict mode)

在事件处理程序中

当一个函数被调用为 DOM 事件处理程序时,this 指的是触发该事件的元素。在这个例子中,this 指的是被点击的 <button> 元素。

<button id="my-button" onclick="console.log(this)">Click me</button>
<!-- Logs the button element -->

使用 JavaScript 设置事件处理程序时,this 也指的是接收事件的元素。

document.getElementById('my-button').addEventListener('click', function () {
console.log(this); // Logs the button element
});

如上所述,ES2015 引入了 箭头函数,它使用 封闭的词法范围。这通常很方便,但确实阻止了调用者通过 .call/.apply/.bind 定义 this 上下文。其中一个后果是,如果您使用箭头函数定义 .addEventListener() 的回调参数,DOM 事件处理程序将无法在您的事件处理程序函数中正确绑定 this

document.getElementById('my-button').addEventListener('click', () => {
console.log(this); // Window / undefined (depending on whether strict mode) instead of the button element.
});

总而言之,JavaScript 中的 this 指的是函数或脚本的当前执行上下文,其值可以根据使用它的上下文而改变。理解 this 的工作方式对于构建健壮且可维护的 JavaScript 应用程序至关重要。

延伸阅读

在GitHub上编辑