我注意到this网站上似乎没有明确解释关键字是什么以及它是如何在 JavaScript 中正确(和错误地)使用的。
this
我目睹了它的一些非常奇怪的行为,并且无法理解它为什么会发生。
如何this工作以及何时应该使用它?
this是 JavaScript 中的关键字,是执行上下文的属性。它的主要用途是在函数和构造函数中。
我建议先阅读Mike West的文章JavaScript 中的作用域(存档)。这是对thisJavaScript 中作用域链和作用域链概念的出色而友好的介绍。的规则this非常简单(如果您坚持最佳实践)。
的ECMAScript标准定义this经由抽象操作(缩写AO)ResolveThisBinding:
[AO] ResolveThisBinding […]this使用正在运行的执行上下文的 LexicalEnvironment 来确定关键字的绑定。[脚步]: 令envRec为GetThisEnvironment ()。 返回 ?envRec .GetThisBinding()。
[AO] ResolveThisBinding […]this使用正在运行的执行上下文的 LexicalEnvironment 来确定关键字的绑定。[脚步]:
全局环境记录、模块环境记录和函数环境记录都有自己的 GetThisBinding 方法。
所述GetThisEnvironment AO找到当前正在运行的执行上下文的LexicalEnvironment和查找最接近方兴未艾环境记录(通过迭代地访问他们的[[OuterEnv]]特性),其具有这种结合(即HasThisBinding返回真)。此过程以三种环境记录类型之一结束。
的值this通常取决于代码是否处于严格模式。
GetThisBinding 的返回值反映了this当前执行上下文的值,因此无论何时建立新的执行上下文,都会this解析为一个不同的值。这也可能在修改当前执行上下文时发生。以下小节列出了可能发生这种情况的五种情况。
您可以将代码示例放在AST 资源管理器中以遵循规范详细信息。
这是在顶层评估的脚本代码,例如直接在 a 中<script>:
<script>
<script> // Global context console.log(this); // Logs global object. setTimeout(function(){ console.log("Not global context"); }); </script>
当在脚本的初始全局执行上下文中时,评估this会导致GetThisBinding采取以下步骤:
全局环境记录envRec […] [这样做]的 GetThisBinding 具体方法: 返回envRec .[[GlobalThisValue]]。
全局环境记录envRec […] [这样做]的 GetThisBinding 具体方法:
全局环境记录的 [[GlobalThisValue]] 属性始终设置为主机定义的全局对象,可通过globalThis(window在 Web 上,global在 Node.js 上;MDN上的文档)访问该对象。按照InitializeHostDefinedRealm的步骤了解 [[GlobalThisValue]] 属性是如何形成的。
globalThis
window
global
模块已在 ECMAScript 2015 中引入。
这适用于模块,例如直接在 a 内部时<script type="module">,而不是简单的<script>.
<script type="module">
在模块的初始全局执行上下文中,求值this会导致GetThisBinding采取以下步骤:
模块环境记录的 GetThisBinding 具体方法 […] [这样做]: 返回undefined。
模块环境记录的 GetThisBinding 具体方法 […] [这样做]:
在模块中,的值this总是undefined在全局上下文中。模块隐式处于严格模式。
undefined
有两种eval调用:直接调用和间接调用。这种区别自 ECMAScript 第 5 版起就存在。
eval
eval(
);
(eval)(
((eval))(
eval?.(
)
(
, eval)(
window.eval(
eval.call(
,
const aliasEval1 = eval; window.aliasEval2 = eval;
aliasEval1(
aliasEval2(
const originalEval = eval; window.eval = (x) => originalEval(x);
请参阅chuckj 对“) JavaScript 中的](1, eval)(‘this’) vs eval(‘this’)”的回答?和Dmitry Soshnikov 的 ECMA-262-5 的详细信息——第 2 章:严格模式(已存档),了解何时可能使用间接eval()调用。
eval()
PerformEval执行eval代码。它创建一个新的声明性环境记录作为其 LexicalEnvironment,这是GetThisEnvironment 从中获取this值的地方。
然后,如果this出现在eval代码中,环境记录的GetThisBinding方法发现GetThisEnvironment被称为其返回值。
并且创建的声明性环境记录取决于eval调用是直接的还是间接的:
意思是:
怎么样new Function? — new Function类似于eval,但它不会立即调用代码;它创建了一个函数。一本装订任何地方都不会在这里适用,除非当函数被调用,如在下一小节解释其正常工作。
new Function
调用函数时会出现输入函数代码。
调用函数有四类语法。
EvaluateCall
AO是这三个执行:
3
标记模板
并为此执行
EvaluateNew
:
实际的函数调用发生在Call AO 处,调用时使用根据上下文确定的thisValue;此参数在与调用相关的一长串调用中传递。Call调用函数的[[Call]]内部槽。这将调用PrepareForOrdinaryCall,其中创建了一个新的函数环境记录:
甲功能环境记录是一种声明环境记录被用于表示功能的顶层范围和,如果函数不是ArrowFunction,提供了一种this结合。如果函数不是ArrowFunction函数并引用super,则其函数环境记录还包含用于super从函数内部执行方法调用的状态。
super
另外,在一个函数 Environment Record 中有 [[ThisValue]] 字段:
这是this用于此函数调用的值。
该NewFunctionEnvironment通话也设置功能环境的[[ThisBindingStatus]]属性。
[[Call]]还调用OrdinaryCallBindThis,其中适当的thisArgument是基于以下因素确定的:
一旦确定,对新创建的函数 Environment Record的BindThisValue方法的最终调用实际上将 [[ThisValue]] 字段设置为thisArgument。
最后,这个字段是函数 Environment Record 的 GetThisBinding AOthis从以下位置获取值的地方:
函数 Environment Record envRec […] [这样做]的 GetThisBinding 具体方法: […] \3. 返回envRec .[[ThisValue]]。
函数 Environment Record envRec […] [这样做]的 GetThisBinding 具体方法:
[…] \3. 返回envRec .[[ThisValue]]。
同样,该值的确切确定方式取决于许多因素;这只是一个总体概述。有了这个技术背景,让我们检查所有具体的例子。
当评估箭头函数时,函数对象的 [[ThisMode]] 内部槽在OrdinaryFunctionCreate 中设置为“词法”。
在OrdinaryCallBindThis,它接受一个函数F:
令thisMode为F .[[ThisMode]]。 如果thisMode是词法的,则返回 NormalCompletion( undefined)。[…]
这只是意味着将跳过绑定this的算法的其余部分。箭头函数不绑定自己的this值。
那么,this箭头函数内部是什么?回顾ResolveThisBinding和GetThisEnvironment,HasThisBinding 方法显式返回false。
HasThisBinding 函数的具体方法 Environment Record envRec […] [这样做]: 如果envRec .[[ThisBindingStatus]] 是lexical,则返回false;否则,返回true。
HasThisBinding 函数的具体方法 Environment Record envRec […] [这样做]:
因此,外部环境被迭代地查找。该过程将在具有this绑定的三个环境之一中结束。
这只是意味着,在箭头函数体中,this来自箭头函数的词法范围,或者换句话说(来自[箭头函数与函数声明/表达式:它们是否等效/可交换?):
箭头函数没有自己的this[…] 绑定。相反,[这个标识符]像任何其他变量一样在词法范围内解析。这意味着在箭头函数内部,this[引用] 到定义this箭头函数的环境中的[值] (即箭头函数的“外部”)。
在正常功能(function,方法),this来确定由所述函数是如何被调用。
function
这就是这些“语法变体”派上用场的地方。
考虑这个包含函数的对象:
const refObj = { func: function(){ console.log(this); } };
或者:
const refObj = { func(){ console.log(this); } };
在以下任何函数调用中,this里面的值func都是refObj. 1
func
refObj
refObj.func()
refObj["func"]()
refObj?.func()
refObj.func?.()
如果被调用的函数在语法上是一个基对象的属性,那么这个基将是调用的“引用”,在通常情况下,它是 的值this。上面链接的评估步骤对此进行了解释;例如,在refObj.func()(或refObj["func"]())中,CallMemberExpression是整个表达式refObj.func(),它由MemberExpression refObj.func和Arguments 组成 ()。
refObj.func
()
而且,refObj.func并refObj扮演三个角色,每个角色:
refObj.func作为值是可调用的函数对象;相应的参考用于确定this绑定。
可选链接和标记模板示例的工作方式非常相似:基本上,引用是在?.()、 之前```或 之前的所有内容()`。
?.()
```或 之前的所有内容
EvaluateCall使用该引用的IsPropertyReference在语法上确定它是否是对象的属性。它试图获取引用的 [[Base]] 属性(例如refObj,当应用于refObj.func; 或foo.bar应用于 时foo.bar.baz)。如果将其写为属性,则GetThisValue将获取此 [[Base]] 属性并将其用作此值。
foo.bar
foo.bar.baz
注意:关于.getter/setter 的工作方式与方法相同this。简单的属性不会影响执行上下文,例如这里this是在全局范围内:
const o = { a: 1, b: this.a, // Is `globalThis.a`. [this.a]: 2 // Refers to `globalThis.a`. };
with
没有基引用的调用通常是不作为属性调用的函数。例如:
func(); // As opposed to `refObj.func();`.
在传递或分配方法或使用逗号运算符时也会发生这种情况。这是参考记录和值之间的差异相关的地方。
const g = (f) => f(); // No base ref. const h = refObj.func; g(refObj.func); h(); // No base ref. (0, refObj.func)(); // Another common pattern to remove the base ref.
EvaluateCall电话呼叫与thisValue的不确定这里。这在OrdinaryCallBindThis(F:函数对象;thisArgument:传递给Call的thisValue)中有所不同:
令thisMode为F .[[ThisMode]]。 […] 如果thisMode是严格的,则让thisValue为thisArgument。 别的, 如果 thisArgument 为 undefined 或 null ,则 令globalEnv为calleeRealm .[[GlobalEnv]]。 […] 令thisValue为globalEnv .[[GlobalThisValue]]。 别的, 让thisValue成为!ToObject (thisArgument)。 注意:ToObject生成包装对象 […]。 […]
[…]
如果thisMode是严格的,则让thisValue为thisArgument。
别的,
如果
thisArgument
为
或
null
,则
注意:第 5 步在严格模式下将 的实际值设置this为提供的thisArgument - undefined在这种情况下。在“草率模式”中,未定义或空的thisArgument导致this成为全局this值。
如果IsPropertyReference返回false,则EvaluateCall采取以下步骤:
让refEnv为ref .[[Base]]。 断言:refEnv是环境记录。 让thisValue为refEnv .WithBaseObject()。
这就是未定义的thisValue可能来自:refEnv。WithBaseObject()总是不确定的,除了在with声明。在这种情况下,thisValue将是绑定对象。
还有Symbol.unscopables(MDN 上的文档)来控制with绑定行为。
Symbol.unscopables
总结一下,到目前为止:
function f1(){ console.log(this); } function f2(){ console.log(this); } function f3(){ console.log(this); } const o = { f1, f2, [symbol.unscopables]: { f2: true } }; f1(); // Logs `globalThis`. with(o){ f1(); // Logs `o`. f2(); // `f2` is unscopable, so this logs `globalThis`. f3(); // `f3` is not on `o`, so this logs `globalThis`. }
和:
"use strict"; function f(){ console.log(this); } f(); // Logs `undefined`. // `with` statements are not allowed in strict-mode code.
需要注意的是评估时this,它并不重要*,其中*一个正常的函数定义。
.call
.apply
.bind
OrdinaryCallBindThis的第 5 步与第 6.2 步(规范中的 6.b)相结合的另一个结果是,仅在“草率”模式下才将原始this值强制转换为对象。
为了检查这一点,让我们为this值引入另一个来源:覆盖this绑定的三个方法:4
Function.prototype.apply(thisArg, argArray)
Function.prototype.
call
bind
(thisArg, ...args)
.bind创建一个绑定函数,其this绑定设置为thisArg并且不能再次更改。.call并.apply立即调用该函数,并将this绑定设置为thisArg。
.call`并使用指定的*thisArg*`.apply`直接映射到[Call](https://tc39.es/ecma262/#sec-call)。使用[BoundFunctionCreate](https://tc39.es/ecma262/#sec-boundfunctioncreate)创建绑定函数。它们有*自己的*[[[Call\]] 方法](https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist),用于查找函数对象的 [[BoundThis]] 内部插槽。`.bind
设置自定义this值的示例:
function f(){ console.log(this);}const myObj = {}, g = f.bind(myObj), h = (m) => m();// All of these log `myObj`.g();f.bind(myObj)();f.call(myObj);h(g);
对于对象,这在严格模式和非严格模式下是一样的。
现在,尝试提供一个原始值:
function f(){ console.log(this);}const myString = "s", g = f.bind(myString);g(); // Logs `String { "s" }`.f.call(myString); // Logs `String { "s" }`.
在非严格模式下,原语被强制转换为它们的对象包装形式。它与您在调用Object("s")or时获得的对象类型相同new String("s")。在严格模式下,您可以使用原语:
Object("s")
new String("s")
"use strict";function f(){ console.log(this);}const myString = "s", g = f.bind(myString);g(); // Logs `"s"`.f.call(myString); // Logs `"s"`.
库使用这些方法,例如 jQuery 将 设置为this此处选择的 DOM 元素:
$("button").click(function(){ console.log(this); // Logs the clicked button.});
new
当使用new运算符将函数作为构造函数调用时,EvaluateNew调用Construct,后者调用[[Construct]] 方法。如果函数是一个基类的构造(即,不是class extends… {… }),它设置thisArgument从构造函数的原型创建新的对象。this在构造函数中设置的属性将最终出现在生成的实例对象上。this隐式返回,除非您显式返回您自己的非原始值。
class extends
{
}
Aclass是一种创建构造函数的新方法,在 ECMAScript 2015 中引入。
class
function Old(a){ this.p = a;}const o = new Old(1);console.log(o); // Logs `Old { p: 1 }`.class New{ constructor(a){ this.p = a; }}const n = new New(1);console.log(n); // Logs `New { p: 1 }`.
类定义隐含在严格模式中:
class A{ m1(){ return this; } m2(){ const m1 = this.m1; console.log(m1()); }}new A().m2(); // Logs `undefined`.
行为的例外new是class extends… {… },如上所述。派生类不会在调用时立即设置它们的this值;他们只在super调用后这样做(在没有自己的情况下隐式发生constructor)。使用this呼叫之前super是不允许的。
constructor
调用使用super调用的词法范围(函数环境记录)的this值调用超级构造函数。GetThisValue有一个特殊的super调用规则。它使用BindThisValue设置this为该环境记录。
class DerivedNew extends New{ constructor(a, a2){ // Using `this` before `super` results in a ReferenceError. super(a); this.p2 = a2; }}const n2 = new DerivedNew(1, 2);console.log(n2); // Logs `DerivedNew { p: 1, p2: 2 }`.
ECMAScript 2022 中引入了实例字段和静态字段。
当class评估 a 时,执行ClassDefinitionEvaluation,修改正在运行的执行上下文。对于每个ClassElement:
私有字段(例如#x)和方法被添加到 PrivateEnvironment。
#x
静态块目前是TC39 第 3 阶段的提案。静态块的工作方式与静态字段和方法相同:this在它们内部是指类本身。
请注意,在方法和 getter/setter 中,this就像在普通函数属性中一样工作。
class Demo{ a = this; b(){ return this; } static c = this; static d(){ return this; } // Getters, setters, private modifiers are also possible. } const demo = new Demo; console.log(demo.a, demo.b()); // Both log `demo`. console.log(Demo.c, Demo.d()); // Both log `Demo`.
1 :(o.f)()相当于o.f(); (f)()相当于f()。这在这篇 2ality 文章存档)中进行了解释。特别是查看ParenthesizedExpression是如何计算的。
(o.f)()
o.f()
(f)()
f()
2:它必须是MemberExpression,不能是属性,必须有一个 [[ReferencedName]] 正好是“eval”,并且必须是 %eval% 内在对象。
3:每当规范说“让ref成为对X求值的结果。”,那么X是一些您需要查找求值步骤的表达式。例如,评估MemberExpression或CallExpression是这些算法之一的结果。其中一些导致参考记录。
4:也有允许提供一些其他本地和主机方法这个值,特别是Array.prototype.map,Array.prototype.forEach等接受一个thisArg作为他们的第二个参数。任何人都可以做自己的方法来改变this一样(func, thisArg) => func.bind(thisArg),(func, thisArg) => func.call(thisArg)等像往常一样,MDN提供了巨大的文档。
Array.prototype.map
Array.prototype.forEach
(func, thisArg) => func.bind(thisArg)
(func, thisArg) => func.call(thisArg)
对于每个代码片段,回答问题:“this标记行的值是多少?为什么?” .
要显示答案,请单击灰色框。
js if(true){ console.log(this); // What is `this` here? }
``
```js const obj = {};
function myFun(){ return { // What is this here? “is obj”: this === obj, “is globalThis”: this === globalThis }; }
obj.method = myFun;
console.log(obj.method());
```
Run code snippet
Expand snippet
``````
``js const obj = { myMethod: function(){ return { // What isthis` here? “is obj”: this === obj, “is globalThis”: this === globalThis }; } }, myFun = obj.myMethod;
``js const obj = { myMethod: function(){ return { // What is
console.log(myFun());
``````````
``js const obj = { myFun: () => ({ // What isthis` here? “is obj”: this === obj, “is globalThis”: this === globalThis }) };
``js const obj = { myFun: () => ({ // What is
console.log(obj.myFun());
``js function myFun(){ console.log(this); // What is
const obj = { myMethod: function(){ eval(“myFun()”); } };
obj.myMethod(); ```
````````
``js function myFun() { // What isthis` here? return { “is obj”: this === obj, “is globalThis”: this === globalThis }; }
``js function myFun() { // What is
const obj = {};
console.log(myFun.call(obj));
``js class MyCls{ arrow = () => ({ // What isthis` here? “is MyCls”: this === MyCls, “is globalThis”: this === globalThis, “is instance”: this instanceof MyCls }); }
``js class MyCls{ arrow = () => ({ // What is
console.log(new MyCls().arrow());