this
是 JavaScript 中的一个关键字,是执行上下文的一个属性。它主要用于函数和构造函数。它的规则 this
非常简单(如果你遵循最佳实践)。
规范中 this
的技术描述
ECMAScript 标准 通过抽象操作(缩写为 this
这一点 AO ) ResolveThisBinding :
正在运行的执行上下文 this
的 LexicalEnvironment 确定关键字的绑定 。[步骤]:
-
让 envRec 成为 GetThisEnvironment ()。
-
返回? envRec.GetThisBinding ()。
全局环境记录、 , 模块环境记录 和 函数环境记录 各自都有自己的 GetThisBinding 方法。
AO 查找当前 GetThisEnvironment 正在运行的执行上下文 的 LexicalEnvironment,并查找最近的具有 this this 的 true 。此过程以三种环境记录类型之一结束。
的值 this
通常取决于代码是否处于 严格模式 .
GetThisBinding 的返回值反映了当前执行上下文的值 this
,因此每当建立新的执行上下文时, this
都会解析为一个不同的值。当修改当前执行上下文时也会发生这种情况。以下小节列出了可能发生这种情况的五种情况。
您可以将代码示例放入 AST 资源管理器 ,以便跟踪规范详细信息。
1. 脚本中的全局执行上下文
这是在顶层评估的脚本代码,例如直接在内部 <script>
:
<script>
// Global context
console.log(this); // Logs global object.
setTimeout(function(){
console.log("Not global context");
});
</script>
当处于脚本的初始全局执行上下文中时,评估 this
会导致 GetThisBinding 采取以下步骤:
的 GetThisBinding 具体方法 envRec […] [执行以下操作]:
-
返回 envRec .[[GlobalThisValue]].
全局环境记录的 [[GlobalThisValue]] 属性始终设置为主机定义的 全局对象 ,该对象可通过 globalThis
( window
在 Web 上、 global
在 Node.js 上; 在 MDN 上查看文档 的步骤 InitializeHostDefinedRealm 了解 [[GlobalThisValue]] 属性是如何产生的。
ECMAScript 2015 中引入了模块。
这适用于模块,例如,当直接位于 内部时 <script type="module">
,而不是简单的 <script>
.
当处于模块的初始全局执行上下文中时,评估 this
会导致 GetThisBinding 采取以下步骤:
模块环境记录的 GetThisBinding 具体方法 […] [执行以下操作]:
-
返回 undefined .
在模块中,的值 this
始终 undefined
位于全局上下文中。模块隐式处于 严格模式 .
3. 输入 eval 代码
有两种类型的 eval
调用: 直接调用 和 间接调用 。这种区别从 ECMAScript 第 5 版开始就存在了。
-
直接
eval
调用通常看起来像 eval(
... );
或 (eval)(
... );
(或 ((eval))(
... );
等)。1只有当调用表达式符合狭窄模式时, 它才是 直接的。2
-
间接
eval
以任何其他方式 eval
调用函数引用 eval?.(
等。给定,它。另外,给定………………………… )
, (
… , eval)(
… )
, window.eval(
… )
, eval.call(
… ,
… )
也可能为 const aliasEval1 = eval; window.aliasEval2 = eval;
…… aliasEval1(
… )
, aliasEval2(
… )
, const originalEval = eval; window.eval = (x) => originalEval(x);
调用 eval(
… )
也是间接的。
请参阅 chuckj’s answer to “(1, eval)('this') vs eval('this') in JavaScript?” 以及 Dmitry Soshnikov 的 ECMA-262-5 详细信息 – 第 2 章:严格模式 ( 已存档 ),了解何时可以使用间接 eval()
调用。
PerformEval 执行 eval
代码。它创建一个新的 声明性环境记录 作为其词法环境, GetThisEnvironment 获取 this
值。
然后,如果 this
出现在 eval
则调用 GetThisEnvironment 找到的环境记录的 GetThisBinding 方法
并且创建的 声明性环境记录 取决于 eval
调用是直接的还是间接的:
这意味着:
-
在直接求值中,
this
值不会改变;它是从调用的词法作用域中获取的 eval
.
-
在间接求值中,
this
值是全局对象 ( globalThis
)。
What about new Function
? —— new Function
与 类似 eval
,但它不会立即调用代码;它会创建一个函数。 this 绑定在这里不适用于任何地方,除非调用函数,这将正常工作,如下一小节所述。
4. 输入 功能 代码
调用 函数 时输入函数代码
调用函数的语法有四类。
实际函数调用发生在 调用 AO 中,它使用 thisValue ;此参数在与调用相关的长链中传递。Call Call 函数的 [[Call]] 槽 PrepareForOrdinaryCall 创建 一个新的 函数环境记录
函数 环境记录 是一种声明性环境记录,用于表示函数的顶级范围,如果函数不是 , ArrowFunction 则提供 this
绑定。如果函数不是 ArrowFunction 函数并且引用 super
在函数内部 super
执行方法调用的状态
另外,函数环境记录中有一个[[ThisValue]]字段:
这是 this
用于本次函数调用的值。
调用 NewFunctionEnvironment 还设置函数环境的 [[ThisBindingStatus]] 属性。
[[Call]] 还调用 OrdinaryCallBindThis ,其中适当的 thisArgument 根据以下内容确定:
-
原始参考,
-
函数的类型,以及
-
代码是否处于 严格模式 .
一旦确定,最后调用 新创建的函数 Environment Record 的 BindThisValue thisArgument .
最后,这个字段是 函数环境记录的 GetThisBinding AO 获取其值的 this
:
函数环境记录 envRec […] [执行此操作]:
[…]
3. 返回 envRec .[[ThisValue]].
这个 究竟如何 确定取决于许多因素;这只是一个总体概述。有了这些技术背景,让我们来研究一下所有具体的例子。
箭头函数
当 箭头函数 在 “lexical” in OrdinaryFunctionCreate .
在 OrdinaryCallBindThis ,它采用函数 F :
-
令 thisMode 为 F .[[ThisMode]].
-
如果 th是Mode is 词汇的 ,则返回NormalCompletion(
undefined
)。[…]
的其余算法 this 。箭头函数不绑定其自己的 this 值。
那么, this
箭头函数里面是什么呢?回顾 ResolveThisBinding 和 GetThisEnvironment , HasThisBinding method explicitly returns false .
函数环境记录 envRec […] [执行以下操作]:
-
如果 envRec .[[ThisBindingStatus]] 是 词汇的 ,则返回 false ;否则返回 true .
因此,将迭代地查找外部环境。该过程将结束于三个具有 this 绑定的环境之一。
这只是意味着, in arrow function bodies, this
comes from the lexical scope of the arrow function ,或者换句话说(来自 箭头函数与函数声明/表达式:它们是否等效/可交换? ):
箭头函数没有自己的 this
[…] 绑定。相反,[此标识符] 像任何其他变量一样在词法作用域中解析。这意味着在箭头函数内部, this
定义 this
环境中的 [值 (即箭头函数“外部”)。
在普通函数( function
, 方法 )中, this
由函数的调用方式 决定 .
这就是这些“语法变体”派上用场的地方。
考虑这个包含一个函数的对象:
const refObj = {
func: function(){
console.log(this);
}
};
或者:
const refObj = {
func(){
console.log(this);
}
};
在以下任何函数调用中, this
里面的值 func
都将为 refObj
.1
-
refObj.func()
-
refObj["func"]()
-
refObj?.func()
-
refObj.func?.()
-
refObj.func``
如果被调用函数在语法上是基对象的属性,那么这个基对象将是调用的“引用”,通常情况下,它将是 的值 this
。这可以通过上面链接的求值步骤来解释;例如,在 refObj.func()
(或 refObj["func"]()
) 中, CallMemberExpression 是整个表达式 refObj.func()
MemberExpression MemberExpression refObj.func
组成 Arguments ()
.
而且, refObj.func
各 refObj
扮演三个角色:
-
它们都是表达方式,
-
它们都是参考,并且
-
它们都是价值观。
refObj.func
作为 值 是可调用函数对象;相应的 引用 用于确定 this
绑定。
可选链式调用和标记模板示例的工作原理非常相似:基本上,引用是 之前 ?.()
、 之前 ``
或 之前的 ()
.
EvaluateCall 使用 IsPropertyReference 来确定它是否是对象的属性。它试图获取引用的 [[Base]] 属性(例如 refObj
,当应用于 时 refObj.func
;或 foo.bar
当应用于 时 foo.bar.baz
)。如果它被写为属性,则 GetThisValue 将获取此 [[Base]] 属性并将其用作 this 值。
注意: Getters / Setters 的 工作方式与方法相同 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();`.
传递或分配方法 或使用 逗号运算符 时也会发生这种情况 。这就是引用记录和值之间的区别所在。
注意函数 j
:按照规范,您会注意到 j
只能返回函数对象(值)本身,而不能返回引用记录。因此基本引用 refObj
会丢失。
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.
Evaluate未定义 使用 thisValue 的 传递给 调用 Call 中有所不同 OrdinaryCallBindThis ( F :函数对象; thisArgument : thisValue Call Call ):
-
令 thisMode 为 F .[[ThisMode]].
[…]
-
如果 th是Mode is strict ,则让 thisValue 为 thisArgument .
-
别的,
- If thisArgument is undefined or null , then
- Let globalEnv be calleeRealm .[[GlobalEnv]].
- […]
- Let thisValue be globalEnv .[[GlobalThisValue]].
- Else,
- Let thisValue be ! ToObject (thisArgument).
- NOTE: ToObject produces wrapper objects […].
[…]
,步骤 5 在严格模式下 this
的实际值设置 为提供的 thisArgument undefined
。在“松散模式”下,未定义或 null 的 thisArgument 将导致 this
全局 this 值。
如果 IsPropertyReference 返回 false ,则 EvaluateCall 采取以下步骤:
-
让 refEnv 成为 ref .[[Base]].
-
断言: refEnv 是一个环境记录。
-
让 thisValue 成为 refEnv.WithBaseObject ()。
这就是未定义的 thisValue 可能来自的地方: refEnv . WithBaseObject () 始终 未定义, , 除非 with with
。在这种情况下, thisValue 将是绑定对象。
还有 Symbol.unscopables
( MDN 上的文档 )来控制 with
绑定行为。
总结一下,到目前为止:
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
, it doesn’t matter where a normal function is defined .
的步骤 5 OrdinaryCallBindThis 与步骤 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
并 .apply
直接映射到 Call ,使用指定的 thisArg . .bind
创建绑定函数 BoundFunctionCreate 。它们有 自己的 [[Call]] 方法 ,用于查找函数对象的 [[BoundThis]] 内部槽。
设置自定义 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")
时获得的对象类型相同 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
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 A{
m1(){
return this;
}
m2(){
const m1 = this.m1;
console.log(m1());
}
}
new A().m2(); // Logs `undefined`.
极好的
,使用 行为的例外 new
是 class extends
… {
… }
。派生类不会在调用时立即设置其 this 值;它们仅在通过一系列 super
调用到达基类后才这样做(在没有 own 的情况下隐式发生 constructor
不允许 this
在调用之前 super
使用
使用 super
调用的词法范围(函数环境记录)的 this 调用 super 构造函数 对调用有一条特殊规则 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 }`.
5. 评估类字段
ECMAScript 2022 中引入了实例字段和静态字段。
评估 class
a 时 将执行 ClassDefinitionEvaluation 正在运行的执行上下文 。对于每个 ClassElement :
-
如果字段是静态的,则
this
引用类本身,
-
如果字段不是静态的,则
this
引用实例。
私有字段(例如 #x
)和方法被添加到PrivateEnvironment。
静态块 目前是 TC39 第三阶段提案 。静态块的工作方式与静态字段和方法相同: 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 文章 ( ( 中有解释 。具体请参见 how a ParenthesizedExpression is evaluated .
2:它必须是 MemberExpression ,不能是属性,必须具有恰好为 \'eval\' ,并且必须是 %eval% 内在对象。
3:每当规范说 “让 ref X 的结果 X 就是某个表达式,您需要找到其评估步骤。例如,评估 MemberExpression or CallExpression 这些算法 之一的结果 。其中一些算法会产生 引用记录 .
4:还有其他几种本机和主机方法允许提供 this 值,特别是 Array.prototype.map
, Array.prototype.forEach
等,它们接受 thisArg 作为其第二个参数。任何人都可以创建自己的方法来进行修改 this
例如 (func, thisArg) => func.bind(thisArg)
, (func, thisArg) => func.call(thisArg)
等。与往常一样, MDN 提供了出色的文档。
只是为了好玩,用一些例子来测试你的理解
对于每个代码片段,回答以下问题: “What is the value of this
at the marked line? Why?” .
要显示答案,请单击灰色框。
-
if(true){ console.log(this); // What is `this` here?}
p94
-
const obj = {};function myFun(){ return { // What is `this` here? \'is obj\': this === obj, \'is globalThis\': this === globalThis };}obj.method = myFun;console.log(obj.method());
p95
-
const obj = { myMethod: function(){ return { // What is `this` here? \'is obj\': this === obj, \'is globalThis\': this === globalThis }; } }, myFun = obj.myMethod;console.log(myFun());
p96
-
const obj = { myFun: () => ({ // What is `this` here? \'is obj\': this === obj, \'is globalThis\': this === globalThis }) };console.log(obj.myFun());
p97
-
function myFun(){ console.log(this); // What is `this` here?}const obj = { myMethod: function(){ eval("myFun()"); } };obj.myMethod();
p98
-
function myFun() { // What is `this` here? return { \'is obj\': this === obj, \'is globalThis\': this === globalThis };}const obj = {};console.log(myFun.call(obj));
p99
-
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());
p100