From 2aa5c6dee9e80737a34645e83f25df695d66be39 Mon Sep 17 00:00:00 2001 From: Huan Luo Date: Sat, 4 Nov 2023 18:37:45 +0800 Subject: [PATCH 01/29] [zh-cn]: update the translation of this keyword --- .../reference/operators/this/index.md | 541 ++++++++++-------- 1 file changed, 299 insertions(+), 242 deletions(-) diff --git a/files/zh-cn/web/javascript/reference/operators/this/index.md b/files/zh-cn/web/javascript/reference/operators/this/index.md index 19697bbc731d43..d981fd644cbb06 100644 --- a/files/zh-cn/web/javascript/reference/operators/this/index.md +++ b/files/zh-cn/web/javascript/reference/operators/this/index.md @@ -19,100 +19,214 @@ this ### 值 -当前执行上下文(global、function 或 eval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。 +在非严格模式下,`this` 总是指向一个对象,在严格模式下可以是任意值。有关如何确定该值的更多信息,请参阅下面的描述。 ## 描述 -### 全局上下文 +`this` 的值取决于它出现的上下文:函数、类或全局。 + +### 函数上下文 -无论是否在严格模式下,在全局执行环境中(在任何函数体外部)`this` 都指向全局对象。 +在函数内部,`this` 的值取决于函数如何被调用。可以将 `this` 看作是函数的一个隐藏参数(就像函数定义中声明的参数一样),`this` 是语言在函数体被求值时为你创建的绑定。 + +对于典型的函数,`this` 的值是函数被访问的对象。换句话说,如果函数调用的形式是 `obj.f()`,那么 `this` 就指向 `obj`。例如: ```js -// 在浏览器中,window 对象同时也是全局对象: -console.log(this === window); // true +function getThis() { + return this; +} -a = 37; -console.log(window.a); // 37 +const obj1 = { name: "obj1" }; +const obj2 = { name: "obj2" }; -this.b = "MDN"; -console.log(window.b); // "MDN" -console.log(b); // "MDN" +obj1.getThis = getThis; +obj2.getThis = getThis; + +console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] } +console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] } ``` -> **备注:** 你可以使用 {{jsxref("globalThis")}} 获取全局对象,无论你的代码是否在当前上下文运行。 +注意,虽然函数是相同的,但是基于它是如何被调用的,`this` 的值是不同的。这与函数参数的工作方式类似。 -### 函数上下文 +`this` 的值不是拥有此函数作为自己属性的那个对象,而是用于调用此函数的对象。你可以通过调用对象在[原型链](/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)中的方法来证明这一点。 -在函数内部,`this`的值取决于函数被调用的方式。 +```js +const obj3 = { + __proto__: obj1, + name: "obj3", +}; + +console.log(obj3.getThis()); // { name: 'obj3' } +``` + +`this` 的值总是根据函数如何被调用而改变,即使函数是在创建对象时定义的: + +```js +const obj4 = { + name: "obj4", + getThis() { + return this; + }, +}; + +const obj5 = { name: "obj5" }; + +obj5.getThis = obj4.getThis; +console.log(obj5.getThis()); // { name: 'obj5', getThis: [Function: getThis] } +``` -因为下面的代码不在严格模式下,且 `this` 的值不是由该调用设置的,所以 `this` 的值默认指向全局对象,浏览器中就是 {{domxref("Window", "window")}}。 +如果方法被访问的值是一个原始值,`this` 也将是一个原始值 - 但只有当函数处于严格模式下会如此。 ```js -function f1() { +function getThisStrict() { + "use strict"; // 进入严格模式 return this; } -//在浏览器中: -f1() === window; //在浏览器中,全局对象是 window -//在 Node 中: -f1() === globalThis; +// 仅用于演示 - 你不应该改变内置的原型对象 +Number.prototype.getThisStrict = getThisStrict; +console.log(typeof (1).getThisStrict()); // "number" ``` -然而,在严格模式下,如果进入执行环境时没有设置 `this` 的值,`this` 会保持为 `undefined`,如下: +如果函数在没有被任何东西访问的情况下被调用,`this` 将是 `undefined` - 但只有在函数处于严格模式下会如此。 ```js -function f2() { - "use strict"; // 这里是严格模式 +console.log(typeof getThisStrict()); // "undefined" +``` + +在非严格模式下,一个特殊的过程称为[`this`替换](/zh-CN/docs/Web/JavaScript/Reference/Strict_mode#no_this_substitution)确保 `this` 的值总是一个对象。这意味着: + +- 如果一个函数被调用时 `this` 被设置为 `undefined` 或 `null`,`this` 会被替换为{{jsxref("globalThis")}}。 +- 如果函数被调用时 `this `被设置为一个原始值,`this` 会被替换为原始值的包装对象。 + +```js +function getThis() { return this; } -f2() === undefined; // true +// 仅用于演示 - 你不应该修改内置的原型对象 +Number.prototype.getThis = getThis; +console.log(typeof (1).getThis()); // "object" +console.log(getThis() === globalThis); // true ``` -> **备注:** 在第二个例子中,`this` 应是 [undefined](/zh-CN/docs/Glossary/undefined),因为 `f2` 是被直接调用的,而不是作为对象的属性或方法调用的(如 `window.f2()`)。有一些浏览器最初在支持[严格模式](/zh-CN/docs/Web/JavaScript/Reference/Strict_mode)时没有正确实现这个功能,于是它们错误地返回了`window`对象。 +在典型的函数调用中,`this` 是通过函数的前缀(点之前的部分)隐式传递的,就像一个参数。你也可以使用{{jsxref("Function.prototype.call()")}},{{jsxref("Function.prototype.apply()")}},或{{jsxref("Reflect.apply()")}}方法显式设置 `this` 的值。使用{{jsxref("Function.prototype.bind()")}},你可以创建一个新的函数,无论函数如何被调用,其 `this` 的值都不会改变。当使用这些方法时,如果函数是在非严格模式下,上述 `this` 替换规则仍然适用。 -如果要想把 `this` 的值从一个环境传到另一个,就要用 [`call`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call) 或者[`apply`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) 方法,如下方的示例所示。 +#### 回调函数 -### 类上下文 +当一个函数作为回调函数传递时,`this` 的值取决于如何调用回调,这由API的实现者决定。回调函数通常以 `undefined` 作为 `this` 的值被调用(直接调用,而不附加到任何对象上),这意味着如果函数是在非严格模式,`this` 的值会是全局对象({{jsxref("globalThis")}})。这在[迭代数组方法](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array#迭代方法),[`Promise()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise)构造函数等都是适用的。 -`this` 在 [类](/zh-CN/docs/Web/JavaScript/Reference/Classes) 中的表现与在函数中类似,因为类本质上也是函数,但也有一些区别和注意事项。 +```js +function logThis() { + "use strict"; + console.log(this); +} -在类的构造函数中,`this` 是一个常规对象。类中所有非静态的方法都会被添加到 `this` 的原型中: +[1, 2, 3].forEach(logThis); // undefined, undefined, undefined +``` -```plain -class Example { - constructor() { - const proto = Object.getPrototypeOf(this); - console.log(Object.getOwnPropertyNames(proto)); - } - first(){} - second(){} - static third(){} +一些 API 允许你为回调函数的调用设置一个 `this` 值。例如,所有的迭代数组方法和相关的方法,如{{jsxref("Set.prototype.forEach()")}},都接受一个可选的 `thisArg` 参数。 + +```js +[1, 2, 3].forEach(logThis, { name: "obj" }); +// { name: 'obj' }, { name: 'obj' }, { name: 'obj' } +``` + +偶尔,回调函数会以一个非 `undefined` 的 `this` 值被调用。例如,{{jsxref("JSON.parse()")}}的 `reviver` 参数和{{jsxref("JSON.stringify()")}}的 `replacer` 参数都会把 `this` 设置为正在被解析/序列化的属性所属的对象。 + +#### 箭头函数 + +在[箭头函数](/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions)中,`this` 保留了闭合词法上下文的 `this` 值。换句话说,当对箭头函数求值时,语言不会创建一个新的 `this` 绑定。 + +例如,在全局代码中,无论是否在严格模式下,由于[全局上下文](#全局上下文)绑定,`this` 值总是 `globalThis`。 + +```js +const globalObject = this; +const foo = () => this; +console.log(foo() === globalObject); // true +``` + +箭头函数在其周围的作用域上创建一个 `this` 值的[闭包](/zh-CN/docs/Web/JavaScript/Closures),这意味着箭头函数的行为就像它们是“自动绑定”的——无论如何调用,`this` 都绑定到函数创建时的值(在上面的例子中,是全局对象)。在其他函数内部创建的箭头函数也是如此:它们的 `this` 值保持为闭合词法上下文的 `this`。[参见下面的例子](#箭头函数中的_this)。 + +此外,当使用 `call()`,`bind()`,或 `apply()` 调用箭头函数时,`thisArg` 参数会被忽略。不过,你仍然可以使用这些方法传递其他参数。 + +```js +const obj = { name: "obj" }; + +// 尝试使用 call 设置 this +console.log(foo.call(obj) === globalObject); // true + +// 尝试使用 bind 设置 this +const boundFoo = foo.bind(obj); +console.log(boundFoo() === globalObject); // true +``` + +#### 构造函数 + +当一个函数被用作构造函数(使用{{jsxref("Operators/new", "new")}}关键字),无论构造函数是在哪个对象上被访问的,其 `this` 都会被绑定到正在构造的新对象上。除非构造函数返回另一个非原始值,不然 `this` 的值会成为 `new` 表达式的值。 + +```js +function C() { + this.a = 37; +} + +let o = new C(); +console.log(o.a); // 37 + +function C2() { + this.a = 37; + return { a: 38 }; } -new Example(); // ['constructor', 'first', 'second'] +o = new C2(); +console.log(o.a); // 38 ``` -> **备注:** 静态方法不是 this 的属性,它们只是类自身的属性。 +在第二个例子(`C2`)中,因为在构造过程中返回了一个对象,`this` 被绑定的新对象被丢弃。(这基本上使得语句 `this.a = 37;` 成为了死代码。它并不完全是死代码,因为它被执行了,但是它可以被消除而不产生任何外部效果。) -### 派生类 +#### super -不像基类的构造函数,派生类的构造函数没有初始的 `this` 绑定。在构造函数中调用 {{jsxref("Operators/super", "super()")}} 会生成一个 `this` 绑定,并相当于执行如下代码,Base 为基类: +当一个函数以 `super.method()` 的形式被调用时,`method` 函数内的 `this` 与 `super.method()` 调用周围的 `this` 值相同,通常不等于 `super` 所指向的对象。这是因为 `super.method` 不是像上面的对象成员访问——它是一种特殊的语法,有不同的绑定规则。有关示例,请参见[`super`参考](/zh-CN/docs/Web/JavaScript/Reference/Operators/super#calling_methods_from_super)。 -```plain +### 类上下文 + +一个[类](/zh-CN/docs/Web/JavaScript/Reference/Classes)可以被分为两个上下文:静态和实例。[构造函数](/zh-CN/docs/Web/JavaScript/Reference/Classes/constructor)、方法和实例字段初始化器([公有](/zh-CN/docs/Web/JavaScript/Reference/Classes/Public_class_fields)或[私有](/zh-CN/docs/Web/JavaScript/Reference/Classes/Private_class_fields))属于实例上下文。[静态](/zh-CN/docs/Web/JavaScript/Reference/Classes/static)方法、静态字段初始化器和[静态初始化块](/zh-CN/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks)属于静态上下文。`this` 值在每个上下文中都是不同的。 + +类构造函数总是通过 `new` 调用,所以它们的行为与[构造函数](#构造函数)相同:`this` 值是正在创建的新实例。类方法的行为像对象字面量中的方法 —— `this` 值是方法被访问的对象。如果方法没有转移到另一个对象,`this` 通常是类的一个实例。 + +静态方法不是 `this` 的属性。它们是类本身的属性。因此,它们通常在类上访问,`this` 是类(或子类)的值。静态初始化块也是在 `this` 设置为当前类的情况下进行求值的。 + +字段初始化器也在类的上下文中进行求值。实例字段是在 `this` 设置为正在构造的实例的情况下进行求值的。静态字段是在 `this` 设置为当前类的情况下进行求值的。这就是为什么字段初始化器中的箭头函数[对于实例字段绑定到实例,对于静态字段绑定到类](/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions#cannot_be_used_as_methods)。 + +```js +class C { + instanceField = this; + static staticField = this; +} + +const c = new C(); +console.log(c.instanceField === c); // true +console.log(C.staticField === C); // true +``` + +#### 派生类构造函数 + +与基类构造函数不同,派生构造函数没有初始的 `this` 绑定。调用{{jsxref("Operators/super", "super()")}}在构造函数中创建一个 `this` 绑定,基本上和求值以下代码的效果类似,其中 `Base` 是基类: + +```js-nolint this = new Base(); ``` -> **警告:** 在调用 `super()` 之前引用 `this` 会抛出错误。 +> **警告:** 在调用 `super()` 之前引用 `this` 将抛出错误。 -派生类不能在调用 `super()` 之前返回,除非其构造函数返回的是一个对象,或者根本没有构造函数。 +派生类在调用 `super()` 之前不能有返回,除非构造函数返回一个对象(这样 `this` 值就会被覆盖)或者类根本没有构造函数。 -```plain +```js class Base {} class Good extends Base {} class AlsoGood extends Base { constructor() { - return {a: 5}; + return { a: 5 }; } } class Bad extends Base { @@ -121,207 +235,168 @@ class Bad extends Base { new Good(); new AlsoGood(); -new Bad(); // ReferenceError +new Bad(); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor +``` + +### 全局上下文 + +在全局执行上下文中(在任何函数或类之外;可能在全局范围内定义的[块](/zh-CN/docs/Web/JavaScript/Reference/Statements/block)或[箭头函数](#箭头函数)内部),`this` 值取决于脚本运行的执行上下文。像[回调](#回调函数)一样,`this` 值由运行时环境(调用者)确定。 + +在脚本的顶层,无论是否在严格模式下,`this` 会指向{{jsxref("globalThis")}}。这通常与全局对象相同 —— 例如,如果源代码放在HTML的 [`