“这个"是怎么来的关键字工作? [英] How does the "this" keyword work?
问题描述
我注意到,似乎没有明确解释 this
关键字是什么以及它如何在 Stack Overflow 网站上的 JavaScript 中正确(和错误)使用.>
我目睹了它的一些非常奇怪的行为,但无法理解为什么会发生这种情况.
this
如何工作以及何时使用?
this
是 JavaScript 中的关键字,是执行上下文的属性.它的主要用途是在函数和构造函数中.this
的规则非常简单(如果您坚持最佳实践).
规范中this
的技术说明
ECMAScript 标准定义了this
通过抽象操作(缩写为AO)ResolveThisBinding:
[AO] ResolveThisBinding […] 使用 this 的绑定rel="nofollow noreferrer">运行执行上下文.[步骤]:
- 让 envRec 成为 GetThisEnvironment().
- 返回?envRec.GetThisBinding().
全球环境记录、模块环境记录和函数环境记录每个都有自己的 GetThisBinding 方法.
GetThisEnvironment AO 找到当前的 运行执行上下文的 LexicalEnvironment 并找到最近的上升环境记录(通过迭代访问它们的 [[OuterEnv]] 属性)具有 this 绑定(即 HasThisBinding 返回 true).此过程以三种环境记录类型之一结束.
this
的值通常取决于代码是否在 严格模式.
GetThisBinding 的返回值反映了当前执行上下文的 this
的值,因此每当建立新的执行上下文时,this
都会解析为一个不同的值.当当前执行上下文被修改时,也会发生这种情况.以下小节列出了可能发生这种情况的五种情况.
您可以将代码示例放在 AST 资源管理器中,以了解规范详细信息.
1.脚本中的全局执行上下文
这是在顶层评估的脚本代码,例如直接在 中:
当在脚本的初始全局执行上下文中时,评估 this
会导致 GetThisBinding 采取以下步骤:
全局环境记录的 GetThisBinding 具体方法 envRec […] [这样做]:
- 返回 envRec.[[GlobalThisValue]].
全局环境记录的 [[GlobalThisValue]] 属性始终设置为主机定义的 全局对象,可通过 模块已在 ECMAScript 2015 中引入. 这适用于模块,例如当直接在 当在模块的初始全局执行上下文中时,评估 模块环境记录的 GetThisBinding 具体方法 […] [这样做]: 在模块中, 有两种 请参阅 chuckj 对JavaScript 中的(1, eval)('this') vs eval('this')?"的回答? 和 Dmitry Soshnikov 的 ECMA-262-5 详述 – 第 2 章:严格模式 (archived) 用于何时您可能使用间接 PerformEval 执行 那么,如果 而创建的declarative Environment Record取决于 这意味着: 在调用一个函数时会出现输入函数代码. 调用函数有四类语法. 实际的函数调用发生在 Call AO 中,该 AO 使用 thisValue 根据上下文确定;这个参数在与调用相关的一长串调用中传递.Call 调用 [[Call]] 函数的内部槽.这会调用 PrepareForOrdinaryCall,其中新的 函数环境记录被创建: 函数环境记录是一个声明性环境记录,用于表示函数的顶级范围,如果函数不是ArrowFunction,则提供 另外,一个函数Environment Record中有[[ThisValue]]字段: 这是用于此函数调用的 NewFunctionEnvironment 调用还会设置函数环境的 [[ThisBindingStatus]] 属性. [[Call]] 也调用 OrdinaryCallBindThis,其中适当的thisArgument基于: 一旦确定,最终调用新创建的函数Environment Record的BindThisValue方法实际上将 [[ThisValue]] 字段设置为 thisArgument. 最后,这个字段是函数环境记录 GetThisBinding AO 从以下位置获取 函数环境记录的GetThisBinding具体方法envRec […] [这样做]: […] 同样,this 值究竟如何确定取决于许多因素;这只是一个总体概述.有了这个技术背景,让我们来看看所有的具体例子. 当评估一个 箭头函数时,[[ThisMode]] 内部函数对象的槽在OrdinaryFunctionCreate中设置为词法". 在OrdinaryCallBindThis,它接受一个函数F:><块引用> 这意味着跳过绑定this的算法的其余部分.箭头函数不绑定自己的this值. 那么,箭头函数中的 函数环境记录的 HasThisBinding 具体方法 envRec […] [这样做]: 所以外部环境被迭代地查找.该过程将在具有 this 绑定的三个环境之一中结束. 这只是意味着,在箭头函数体中, 箭头函数没有自己的 在普通函数中( 这就是这些语法变体"派上用场的地方. 考虑这个对象包含一个函数: 或者: 在以下任何函数调用中, 如果被调用的函数在语法上是基对象的一个属性,那么这个基将是调用的引用",在通常情况下,它是 而且, 可选链接和标记模板示例的工作方式非常相似:基本上,引用是 EvaluateCall 使用 IsPropertyReference 来确定它是否是一个对象的属性,在语法上.它试图获取引用的 [[Base]] 属性(例如 注意:Getters/Setters 与方法的工作方式相同,关于 没有基引用的调用通常是一个不作为属性调用的函数.例如: 当传递或分配方法,或使用逗号运算符时,也会发生这种情况.这就是参考记录和值之间的差异相关的地方. 注意函数 EvaluateCall 调用 Call 使用 undefined 的 thisValue 在这里.这在 OrdinaryCallBindThis(F:函数对象;thisArgument:传递给 Call 的 thisValue:globalThis
(2.模块
中的全局执行上下文 中时,而不是简单的
.
this
会导致 GetThisBinding 采取以下步骤:this
的值在全局上下文中总是 undefined
.模块隐式地处于严格模式.3.输入eval 代码
eval
调用:直接和间接.这种区别自 ECMAScript 第 5 版起就存在.eval
调用通常看起来像 eval(
…);
或 (eval)(
…);
(或((eval))(
...);
等).1只是direct 如果调用表达式适合窄模式.2eval
调用涉及以任何其他方式调用函数引用eval
.它可以是 eval?.(
...)
, (
..., eval)(
...)
, window.eval(
...)
, eval.call(
...,
...)
等给定 const aliasEval1 = eval;window.aliasEval2 = eval;
,也可以是aliasEval1(
…)
, aliasEval2(
…)代码>.分别给定
const originalEval = eval;window.eval = (x) =>originalEval(x);
,调用 eval(
...)
也是间接的.eval()
调用.eval
代码.它创建一个新的声明性环境记录作为它的词法环境,这是GetThisEnvironment 从中获取 this
值.this
出现在eval
代码中,则是GetThisEnvironment 被调用并返回其值.eval
调用是直接的还是间接的:this
值不会改变;它取自名为 eval
的词法作用域.this
值是全局对象 (globalThis
).新函数
怎么样? — new Function
与 eval
类似,但它不会立即调用代码;它创建了一个函数.this 绑定不适用于此处的任何地方,除非调用函数时,正常工作,如下一小节所述.4.输入函数代码
this
绑定.如果函数不是 ArrowFunction 函数并引用了 super
,则其函数环境记录还包含用于执行 super
方法调用的状态函数内.this
值.this
的值:><块引用>
3.返回envRec.[[ThisValue]].箭头函数
undefined
).[…]this
是什么?回顾 ResolveThisBinding 和 GetThisEnvironment,HasThisBinding 方法显式返回 false.this
来自箭头函数的词法范围,或者换句话说(来自 箭头函数与函数声明/表达式:它们是否等价/可交换?):this
[…] 绑定.相反,[这个标识符]在词法范围内像任何其他变量一样解析.这意味着在箭头函数内部,this
[引用] 到环境中的[this
值] 箭头函数在(即在"箭头函数之外).函数属性
function
,方法),this
由函数的调用方式决定.const refObj = {功能:功能(){控制台日志(这个);}};
const refObj = {功能(){控制台日志(这个);}};
func
中的 this
值将是 refObj
.1refObj.func()
refObj["func"]()
refObj?.func()
refObj.func?.()
refObj.func``
this
的值.上面链接的评估步骤对此进行了解释;例如,在 refObj.func()
(或 refObj[func"]()
)中,CallMemberExpression 是整个表达式 refObj.func()
,它由 refObj.func
和 参数 ()
.refObj.func
和 refObj
分别扮演三个角色:refObj.func
作为 value 是可调用的函数对象;相应的引用用于确定this
绑定.?.()
之前、``
之前或 <代码>().refObj
,当应用于 refObj.func
; 或 foo.bar
应用于 foo.bar.baz
时).如果写成一个属性,那么GetThisValue 会得到这个 [[Base]] 属性并使用它作为 this 值.this
.简单属性不会影响执行上下文,例如在这里,this
在全局范围内:const o = {一:1,b: this.a,//是 `globalThis.a`.[this.a]: 2//指的是`globalThis.a`.};
调用没有基本引用、严格模式和
with代码>
func();//与`refObj.func();`相反.
j
:按照规范,你会注意到j
只能返回函数对象(Value)本身,而不能返回Reference Record.因此基本引用 refObj
丢失了.const g = (f) =>F();//没有基础参考.const h = refObj.func;const j = () =>refObj.func;g(refObj.func);H();//没有基础参考.j()();//没有基础参考.(0, refObj.func)();//另一种删除基础引用的常见模式.
- 让 thisMode 成为 F.[[ThisMode]].
[…]
- 如果thisMode是strict,让thisValue成为thisArgument.
- 否则,
[…]
注意:在这种情况下,在严格模式 — undefined
中,步骤 5 将 this
的实际值设置为提供的 thisArgument.在草率模式"中,未定义或空的 thisArgument 导致 this
成为全局 this 值.
如果 IsPropertyReference 返回 false,则 EvaluateCall 采取以下步骤:
<块引用>- 让 refEnv 成为 ref.[[Base]].
- 断言:refEnv 是环境记录.
- 让 thisValue 成为 refEnv.WithBaseObject().
这是未定义的 thisValue 的来源:refEnv.WithBaseObject() 总是未定义,除了在with
语句.在这种情况下,thisValue 将是绑定对象.
还有 Symbol.unscopables
(MDN 上的文档) 来控制 with
绑定行为.
总结一下,到目前为止:
function f1(){控制台日志(这个);}函数 f2(){控制台日志(这个);}函数 f3(){控制台日志(这个);}常量 o = {f1,f2,[Symbol.unscopeables]:{f2:真}};f1();//记录`globalThis`.与(o){f1();//记录`o`.f2();//`f2` 是不可见的,所以这会记录 `globalThis`.f3();//`f3` 不在 `o` 上,所以这会记录 `globalThis`.}
和:
使用严格";函数 f(){控制台日志(这个);}F();//记录 `undefined`.//严格模式代码中不允许使用 `with` 语句.
请注意,在评估 this
时,在何处定义正常函数并不重要.
.call
, .apply
, .bind
, thisArg 和原语
OrdinaryCallBindThis 步骤 5 的另一个结果,结合步骤 6.2(在规范),是一个原始的 this 值在草率"模式下仅被强制给一个对象.
为了检查这一点,让我们介绍 this 值的另一个来源:覆盖 this 绑定的三个方法:4
Function.prototype.apply(thisArg, argArray)
Function.prototype.
{call
,bind
}(thisArg, ...args)
莉>
.bind
创建绑定函数,其 this 绑定设置为 thisArg 并且无法再次更改..call
和 .apply
立即调用函数,this绑定设置为 thisArg.
.call
和 .apply
直接映射到 调用,使用指定的thisArg..bind
使用 BoundFunctionCreate 创建绑定函数.它们有自己的 [[调用]] 方法,该方法查找函数对象的 [[BoundThis]] 内部槽.
设置自定义this值的示例:
function f(){控制台日志(这个);}const myObj = {},g = f.bind(myObj),h = (m) =>米();//所有这些都记录了 `myObj`.G();f.bind(myObj)();f.call(myObj);小时(克);
对于对象,这在严格和非严格模式下是一样的.
现在,尝试提供一个原始值:
function f(){控制台日志(这个);}const myString = "s",g = f.bind(myString);G();//记录 `String { "s";}`.f.call(myString);//记录 `String { "s";}`.
在非严格模式下,原语被强制转换为它们的对象包装形式.它与您在调用 Object("s")
或 new String("s")
时获得的对象类型相同.在严格模式下,您可以使用原语:
使用严格";函数 f(){控制台日志(这个);}const myString = "s",g = f.bind(myString);G();//记录s".f.call(myString);//记录s".
图书馆使用这些方法,例如jQuery 将 this
设置为此处选择的 DOM 元素:
$("button").click(function(){控制台日志(这个);//记录点击的按钮.});
构造函数、类 和 new
当使用 new
运算符将函数作为构造函数调用时,EvaluateNew 调用 Construct,它调用 [[Construct]] 方法.如果函数是基本构造函数(即不是class extends
...{
...}
),它会设置thisArgument到从构造函数的原型创建的新对象.在构造函数中设置在 this
上的属性将最终出现在生成的实例对象上.this
是隐式返回的,除非您显式返回您自己的非原始值.
A class
是一种创建构造函数的新方法函数,在 ECMAScript 2015 中引入.
function Old(a){this.p = a;}const o = 新旧(1);控制台日志(o);//记录 `Old { p: 1 }`.类新{构造函数(一){this.p = a;}}const n = new New(1);控制台日志(n);//记录 `New { p: 1 }`.
类定义隐含在严格模式中:
class A{m1(){返回这个;}米2(){const m1 = this.m1;控制台日志(m1());}}新的 A().m2();//记录 `undefined`.
super
new
行为的例外是 class extends
...{
...}
,如上所述.派生类不会在调用时立即设置它们的 this 值;只有在通过一系列 super
调用(在没有自己的 constructor
的情况下隐式发生)到达基类时,它们才会这样做.不允许在调用 super
之前使用 this
.
调用 super
调用具有调用的词法范围(函数环境记录)的 this 值的超级构造函数.GetThisValue 对 super
调用有特殊的规则.It uses BindThisValue to set this
to that Environment Record.
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 }`.
5.Evaluating class fields
Instance fields and static fields were introduced in ECMAScript 2022.
When a class
is evaluated, ClassDefinitionEvaluation is performed, modifying the running execution context. For each ClassElement:
- if a field is static, then
this
refers to the class itself, - if a field is not static, then
this
refers to the instance.
Private fields (e.g. #x
) and methods are added to a PrivateEnvironment.
Static blocks are currently a TC39 stage 3 proposal. Static blocks work the same as static fields and methods: this
inside them refers to the class itself.
Note that in methods and getters/setters, this
works just like in normal function properties.
class Demo{a = this;b(){返回这个;}static c = this;static d(){返回这个;}//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)()
is equivalent to o.f()
; (f)()
is equivalent to f()
. This is explained in this 2ality article (archived). Particularly see how a ParenthesizedExpression is evaluated.
2: It must be a MemberExpression, must not be a property, must have a [[ReferencedName]] of exactly "eval", and must be the %eval% intrinsic object.
3: Whenever the specification says Let ref be the result of evaluating X.", then X is some expression that you need to find the evaluation steps for. For example, evaluating a MemberExpression or CallExpression is the result of one of these algorithms. Some of them result in a Reference Record.
4: There are also several other native and host methods that allow providing a this value, notably Array.prototype.map
, Array.prototype.forEach
, etc. that accept a thisArg as their second argument. Anyone can make their own methods to alter this
like (func, thisArg) => func.bind(thisArg)
, (func, thisArg) => func.call(thisArg)
, etc. As always, MDN offers great documentation.
Just for fun, test your understanding with some examples
For each code snippet, answer the question: What is the value of this
at the marked line? Why?".
To reveal the answers, click the gray boxes.
if(true){console.log(this);//What is `this` here?}
globalThis
. The marked line is evaluated in the initial global execution context.const obj = {};function myFun(){return {//What is `this` here?"is obj": this === obj,"is globalThis": this === globalThis};}obj.method = myFun;console.log(obj.method());
obj
. When calling a function as a property of an object, it is called with the this binding set to the base of the referenceobj.method
, i.e.obj
.const obj = {myMethod: function(){return {//What is `this` here?"is obj": this === obj,"is globalThis": this === globalThis};}},myFun = obj.myMethod;console.log(myFun());
globalThis
. Since the function valuemyFun
/obj.myMethod
is not called off of an object, as a property, the this binding will beglobalThis
.This is different from Python, in which accessing a method (obj.myMethod
) creates a bound method object.const obj = {myFun: () => ({//What is `this` here?"is obj": this === obj,"is globalThis": this === globalThis})};console.log(obj.myFun());
globalThis
. Arrow functions don’t create their own this binding. The lexical scope is the same as the initial global scope, sothis
isglobalThis
.function myFun(){console.log(this);//What is `this` here?}const obj = {myMethod: function(){eval("myFun()");}};obj.myMethod();
globalThis
. When evaluating the direct eval call,this
isobj
. However, in the eval code,myFun
is not called off of an object, so the this binding is set to the global object.function myFun() {//What is `this` here?返回 {"is obj": this === obj,"is globalThis": this === globalThis};}const obj = {};console.log(myFun.call(obj));
obj
. The linemyFun.call(obj);
is invoking the special built-in functionFunction.prototype.call
, which acceptsthisArg
as the first argument.class MyCls{arrow = () => ({//What is `this` here?"is MyCls": this === MyCls,"is globalThis": this === globalThis,"is instance": this instanceof MyCls});}console.log(new MyCls().arrow());
It’s the instance of
MyCls
. Arrow functions don’t change the this binding, so it comes from lexical scope. Therefore, this is exactly the same as with the class fields mentioned above, likea = this;
. Try changing it tostatic arrow
. Do you get the result you expect?
I have noticed that there doesn't appear to be a clear explanation of what the this
keyword is and how it is correctly (and incorrectly) used in JavaScript on the Stack Overflow site.
I have witnessed some very strange behaviour with it and have failed to understand why it has occurred.
How does this
work and when should it be used?
this
is a keyword in JavaScript that is a property of an execution context. Its main use is in functions and constructors.
The rules for this
are quite simple (if you stick to best practices).
Technical description of this
in the specification
The ECMAScript standard defines this
via the abstract operation (abbreviated AO) ResolveThisBinding:
The [AO] ResolveThisBinding […] determines the binding of the keyword
this
using the LexicalEnvironment of the running execution context. [Steps]:
- Let envRec be GetThisEnvironment().
- Return ? envRec.GetThisBinding().
Global Environment Records, module Environment Records, and function Environment Records each have their own GetThisBinding method.
The GetThisEnvironment AO finds the current running execution context’s LexicalEnvironment and finds the closest ascendant Environment Record (by iteratively accessing their [[OuterEnv]] properties) which has a this binding (i.e. HasThisBinding returns true). This process ends in one of the three Environment Record types.
The value of this
often depends on whether code is in strict mode.
The return value of GetThisBinding reflects the value of this
of the current execution context, so whenever a new execution context is established, this
resolves to a distinct value. This can also happen when the current execution context is modified. The following subsections list the five cases where this can happen.
You can put the code samples in the AST explorer to follow along with specification details.
1. Global execution context in scripts
This is script code evaluated at the top level, e.g. directly inside a <script>
:
<script>
// Global context
console.log(this); // Logs global object.
setTimeout(function(){
console.log("Not global context");
});
</script>
When in the initial global execution context of a script, evaluating this
causes GetThisBinding to take the following steps:
The GetThisBinding concrete method of a global Environment Record envRec […] [does this]:
- Return envRec.[[GlobalThisValue]].
The [[GlobalThisValue]] property of a global Environment Record is always set to the host-defined global object, which is reachable via globalThis
(window
on Web, global
on Node.js; Docs on MDN). Follow the steps of InitializeHostDefinedRealm to learn how the [[GlobalThisValue]] property comes to be.
2. Global execution context in modules
Modules have been introduced in ECMAScript 2015.
This applies to modules, e.g. when directly inside a <script type="module">
, as opposed to a simple <script>
.
When in the initial global execution context of a module, evaluating this
causes GetThisBinding to take the following steps:
The GetThisBinding concrete method of a module Environment Record […] [does this]:
- Return undefined.
In modules, the value of this
is always undefined
in the global context. Modules are implicitly in strict mode.
3. Entering eval code
There are two kinds of eval
calls: direct and indirect. This distinction exists since the ECMAScript 5th edition.
- A direct
eval
call usually looks likeeval(
…);
or(eval)(
…);
(or((eval))(
…);
, etc.).1 It’s only direct if the call expression fits a narrow pattern.2 - An indirect
eval
call involves calling the function referenceeval
in any other way. It could beeval?.(
…)
,(
…, eval)(
…)
,window.eval(
…)
,eval.call(
…,
…)
, etc. Givenconst aliasEval1 = eval; window.aliasEval2 = eval;
, it would also bealiasEval1(
…)
,aliasEval2(
…)
. Separately, givenconst originalEval = eval; window.eval = (x) => originalEval(x);
, callingeval(
…)
would also be indirect.
See chuckj’s answer to "(1, eval)('this') vs eval('this') in JavaScript?" and Dmitry Soshnikov’s ECMA-262-5 in detail – Chapter 2: Strict Mode (archived) for when you might use an indirect eval()
call.
PerformEval executes the eval
code. It creates a new declarative Environment Record as its LexicalEnvironment, which is where GetThisEnvironment gets the this
value from.
Then, if this
appears in eval
code, the GetThisBinding method of the Environment Record found by GetThisEnvironment is called and its value returned.
And the created declarative Environment Record depends on whether the eval
call was direct or indirect:
- In a direct eval, it will be based on the current running execution context’s LexicalEnvironment.
- In an indirect eval, it will be based on the [[GlobalEnv]] property (a global Environment Record) of the Realm Record which executed the indirect eval.
Which means:
- In a direct eval, the
this
value doesn’t change; it’s taken from the lexical scope that calledeval
. - In an indirect eval, the
this
value is the global object (globalThis
).
What about new Function
? — new Function
is similar to eval
, but it doesn’t call the code immediately; it creates a function. A this binding doesn’t apply anywhere here, except when the function is called, which works normally, as explained in the next subsection.
4. Entering function code
Entering function code occurs when calling a function.
There are four categories of syntax to invoke a function.
- The EvaluateCall AO is performed for these three:3
- And EvaluateNew is performed for this one:3
The actual function call happens at the Call AO, which is called with a thisValue determined from context; this argument is passed along in a long chain of call-related calls. Call calls the [[Call]] internal slot of the function. This calls PrepareForOrdinaryCall where a new function Environment Record is created:
A function Environment Record is a declarative Environment Record that is used to represent the top-level scope of a function and, if the function is not an ArrowFunction, provides a
this
binding. If a function is not an ArrowFunction function and referencessuper
, its function Environment Record also contains the state that is used to performsuper
method invocations from within the function.
In addition, there is the [[ThisValue]] field in a function Environment Record:
This is the
this
value used for this invocation of the function.
The NewFunctionEnvironment call also sets the function environment’s [[ThisBindingStatus]] property.
[[Call]] also calls OrdinaryCallBindThis, where the appropriate thisArgument is determined based on:
- the original reference,
- the kind of the function, and
- whether or not the code is in strict mode.
Once determined, a final call to the BindThisValue method of the newly created function Environment Record actually sets the [[ThisValue]] field to the thisArgument.
Finally, this very field is where a function Environment Record’s GetThisBinding AO gets the value for this
from:
The GetThisBinding concrete method of a function Environment Record envRec […] [does this]:
[…]
3. Return envRec.[[ThisValue]].
Again, how exactly the this value is determined depends on many factors; this was just a general overview. With this technical background, let’s examine all the concrete examples.
Arrow functions
When an arrow function is evaluated, the [[ThisMode]] internal slot of the function object is set to "lexical" in OrdinaryFunctionCreate.
At OrdinaryCallBindThis, which takes a function F:
- Let thisMode be F.[[ThisMode]].
- If thisMode is lexical, return NormalCompletion(
undefined
). […]
which just means that the rest of the algorithm which binds this is skipped. An arrow function does not bind its own this value.
So, what is this
inside an arrow function, then? Looking back at ResolveThisBinding and GetThisEnvironment, the HasThisBinding method explicitly returns false.
The HasThisBinding concrete method of a function Environment Record envRec […] [does this]:
- If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true.
So the outer environment is looked up instead, iteratively. The process will end in one of the three environments that have a this binding.
This just means that, in arrow function bodies, this
comes from the lexical scope of the arrow function, or in other words (from Arrow function vs function declaration / expressions: Are they equivalent / exchangeable?):
Arrow functions don’t have their own
this
[…] binding. Instead, [this identifier is] resolved in the lexical scope like any other variable. That means that inside an arrow function,this
[refers] to the [value ofthis
] in the environment the arrow function is defined in (i.e. "outside" the arrow function).
Function properties
In normal functions (function
, methods), this
is determined by how the function is called.
This is where these "syntax variants" come in handy.
Consider this object containing a function:
const refObj = {
func: function(){
console.log(this);
}
};
Alternatively:
const refObj = {
func(){
console.log(this);
}
};
In any of the following function calls, the this
value inside func
will be refObj
.1
refObj.func()
refObj["func"]()
refObj?.func()
refObj.func?.()
refObj.func``
If the called function is syntactically a property of a base object, then this base will be the "reference" of the call, which, in usual cases, will be the value of this
. This is explained by the evaluation steps linked above; for example, in refObj.func()
(or refObj["func"]()
), the CallMemberExpression is the entire expression refObj.func()
, which consists of the MemberExpression refObj.func
and the Arguments ()
.
But also, refObj.func
and refObj
play three roles, each:
- they’re both expressions,
- they’re both references, and
- they’re both values.
refObj.func
as a value is the callable function object; the corresponding reference is used to determine the this
binding.
The optional chaining and tagged template examples work very similarly: basically, the reference is everything before the ?.()
, before the ``
, or before the ()
.
EvaluateCall uses IsPropertyReference of that reference to determine if it is a property of an object, syntactically. It’s trying to get the [[Base]] property of the reference (which is e.g. refObj
, when applied to refObj.func
; or foo.bar
when applied to foo.bar.baz
). If it is written as a property, then GetThisValue will get this [[Base]] property and use it as the this value.
Note: Getters / Setters work the same way as methods, regarding this
. Simple properties don’t affect the execution context, e.g. here, this
is in global scope:
const o = {
a: 1,
b: this.a, // Is `globalThis.a`.
[this.a]: 2 // Refers to `globalThis.a`.
};
Calls without base reference, strict mode, and with
A call without a base reference is usually a function that isn’t called as a property. For example:
func(); // As opposed to `refObj.func();`.
This also happens when passing or assigning methods, or using the comma operator. This is where the difference between Reference Record and Value is relevant.
Note function j
: following the specification, you will notice that j
can only return the function object (Value) itself, but not a Reference Record. Therefore the base reference refObj
is lost.
const g = (f) => f(); // No base ref.
const h = refObj.func;
const j = () => refObj.func;
g(refObj.func);
h(); // No base ref.
j()(); // No base ref.
(0, refObj.func)(); // Another common pattern to remove the base ref.
EvaluateCall calls Call with a thisValue of undefined here. This makes a difference in OrdinaryCallBindThis (F: the function object; thisArgument: the thisValue passed to Call):
- Let thisMode be F.[[ThisMode]].
[…]
- If thisMode is strict, let thisValue be thisArgument.
- Else,
- If thisArgument is undefined or null, then
- Let globalEnv be calleeRealm.[[GlobalEnv]].
- […]
- Let thisValue be globalEnv.[[GlobalThisValue]].
- Else,
[…]
Note: step 5 sets the actual value of this
to the supplied thisArgument in strict mode — undefined
in this case. In "sloppy mode", an undefined or null thisArgument results in this
being the global this value.
If IsPropertyReference returns false, then EvaluateCall takes these steps:
- Let refEnv be ref.[[Base]].
- Assert: refEnv is an Environment Record.
- Let thisValue be refEnv.WithBaseObject().
This is where an undefined thisValue may come from: refEnv.WithBaseObject() is always undefined, except in with
statements. In this case, thisValue will be the binding object.
There’s also Symbol.unscopables
(Docs on MDN) to control the with
binding behavior.
To summarize, so far:
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`.
}
and:
"use strict";
function f(){
console.log(this);
}
f(); // Logs `undefined`.
// `with` statements are not allowed in strict-mode code.
Note that when evaluating this
, it doesn’t matter where a normal function is defined.
.call
, .apply
, .bind
, thisArg, and primitives
Another consequence of step 5 of OrdinaryCallBindThis, in conjunction with step 6.2 (6.b in the spec), is that a primitive this value is coerced to an object only in "sloppy" mode.
To examine this, let’s introduce another source for the this value: the three methods that override the this binding:4
Function.prototype.apply(thisArg, argArray)
Function.prototype.
{call
,bind
}(thisArg, ...args)
.bind
creates a bound function, whose this binding is set to thisArg and cannot change again. .call
and .apply
call the function immediately, with the this binding set to thisArg.
.call
and .apply
map directly to Call, using the specified thisArg. .bind
creates a bound function with BoundFunctionCreate. These have their own [[Call]] method which looks up the function object’s [[BoundThis]] internal slot.
Examples of setting a custom this value:
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);
For objects, this is the same in strict and non-strict mode.
Now, try to supply a primitive value:
function f(){
console.log(this);
}
const myString = "s",
g = f.bind(myString);
g(); // Logs `String { "s" }`.
f.call(myString); // Logs `String { "s" }`.
In non-strict mode, primitives are coerced to their object-wrapped form. It’s the same kind of object you get when calling Object("s")
or new String("s")
. In strict mode, you can use primitives:
"use strict";
function f(){
console.log(this);
}
const myString = "s",
g = f.bind(myString);
g(); // Logs `"s"`.
f.call(myString); // Logs `"s"`.
Libraries make use of these methods, e.g. jQuery sets the this
to the DOM element selected here:
$("button").click(function(){
console.log(this); // Logs the clicked button.
});
Constructors, classes, and new
When calling a function as a constructor using the new
operator, EvaluateNew calls Construct, which calls the [[Construct]] method. If the function is a base constructor (i.e. not a class extends
…{
…}
), it sets thisArgument to a new object created from the constructor’s prototype. Properties set on this
in the constructor will end up on the resulting instance object. this
is implicitly returned, unless you explicitly return your own non-primitive value.
A class
is a new way of creating constructor functions, introduced in ECMAScript 2015.
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 definitions are implicitly in strict mode:
class A{
m1(){
return this;
}
m2(){
const m1 = this.m1;
console.log(m1());
}
}
new A().m2(); // Logs `undefined`.
super
The exception to the behavior with new
is class extends
…{
…}
, as mentioned above. Derived classes do not immediately set their this value upon invocation; they only do so once the base class is reached through a series of super
calls (happens implicitly without an own constructor
). Using this
before calling super
is not allowed.
Calling super
calls the super constructor with the this value of the lexical scope (the function Environment Record) of the call. GetThisValue has a special rule for super
calls. It uses BindThisValue to set this
to that Environment Record.
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 }`.
5. Evaluating class fields
Instance fields and static fields were introduced in ECMAScript 2022.
When a class
is evaluated, ClassDefinitionEvaluation is performed, modifying the running execution context. For each ClassElement:
- if a field is static, then
this
refers to the class itself, - if a field is not static, then
this
refers to the instance.
Private fields (e.g. #x
) and methods are added to a PrivateEnvironment.
Static blocks are currently a TC39 stage 3 proposal. Static blocks work the same as static fields and methods: this
inside them refers to the class itself.
Note that in methods and getters / setters, this
works just like in normal function properties.
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)()
is equivalent to o.f()
; (f)()
is equivalent to f()
. This is explained in this 2ality article (archived). Particularly see how a ParenthesizedExpression is evaluated.
2: It must be a MemberExpression, must not be a property, must have a [[ReferencedName]] of exactly "eval", and must be the %eval% intrinsic object.
3: Whenever the specification says "Let ref be the result of evaluating X.", then X is some expression that you need to find the evaluation steps for. For example, evaluating a MemberExpression or CallExpression is the result of one of these algorithms. Some of them result in a Reference Record.
4: There are also several other native and host methods that allow providing a this value, notably Array.prototype.map
, Array.prototype.forEach
, etc. that accept a thisArg as their second argument. Anyone can make their own methods to alter this
like (func, thisArg) => func.bind(thisArg)
, (func, thisArg) => func.call(thisArg)
, etc. As always, MDN offers great documentation.
Just for fun, test your understanding with some examples
For each code snippet, answer the question: "What is the value of this
at the marked line? Why?".
To reveal the answers, click the gray boxes.
-
if(true){ console.log(this); // What is `this` here? }
globalThis
. The marked line is evaluated in the initial global execution context. const obj = {}; function myFun(){ return { // What is `this` here? "is obj": this === obj, "is globalThis": this === globalThis }; } obj.method = myFun; console.log(obj.method());
obj
. When calling a function as a property of an object, it is called with the this binding set to the base of the referenceobj.method
, i.e.obj
.const obj = { myMethod: function(){ return { // What is `this` here? "is obj": this === obj, "is globalThis": this === globalThis }; } }, myFun = obj.myMethod; console.log(myFun());
globalThis
. Since the function valuemyFun
/obj.myMethod
is not called off of an object, as a property, the this binding will beglobalThis
. This is different from Python, in which accessing a method (obj.myMethod
) creates a bound method object.const obj = { myFun: () => ({ // What is `this` here? "is obj": this === obj, "is globalThis": this === globalThis }) }; console.log(obj.myFun());
globalThis
. Arrow functions don’t create their own this binding. The lexical scope is the same as the initial global scope, sothis
isglobalThis
.-
function myFun(){ console.log(this); // What is `this` here? } const obj = { myMethod: function(){ eval("myFun()"); } }; obj.myMethod();
globalThis
. When evaluating the direct eval call,this
isobj
. However, in the eval code,myFun
is not called off of an object, so the this binding is set to the global object. function myFun() { // What is `this` here? return { "is obj": this === obj, "is globalThis": this === globalThis }; } const obj = {}; console.log(myFun.call(obj));
obj
. The linemyFun.call(obj);
is invoking the special built-in functionFunction.prototype.call
, which acceptsthisArg
as the first argument.class MyCls{ arrow = () => ({ // What is `this` here? "is MyCls": this === MyCls, "is globalThis": this === globalThis, "is instance": this instanceof MyCls }); } console.log(new MyCls().arrow());
It’s the instance of
MyCls
. Arrow functions don’t change the this binding, so it comes from lexical scope. Therefore, this is exactly the same as with the class fields mentioned above, likea = this;
. Try changing it tostatic arrow
. Do you get the result you expect?
这篇关于“这个"是怎么来的关键字工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!