[英]Functions, arrow functions, closures and ExecutionContext
I'm trying to understand arrow functions in JavaScript and have a few questions regarding how they interact with ExecutionContext/environment and closures.我试图了解 JavaScript 中的箭头函数,并且有一些关于它们如何与 ExecutionContext/environment 和闭包交互的问题。
To the best of my understanding, the "specification" model in JS is that as code gets executed, a stack of ExecutionContext
s is maintained ( 1 , 6 ).据我所知,JS 中的“规范”model 是当代码被执行时,会维护一个
ExecutionContext
堆栈( 1 、 6 )。 Ie at the beginning there's a ExecutionContext
for global, when a function is called a new ExecutionContext
is added for the time of its execution, and when it finishes, it's popped.即在开始时有一个全局的
ExecutionContext
,当 function 被调用时,一个新的ExecutionContext
被添加到它的执行时间,当它完成时,它被弹出。 Ie it matches frames on callstack .即它匹配callstack 上的帧。
Assuming a little bit of simplification (ignore diff between global/function/eval & no let
and const
(ie variable environment
), ExecutionContext
consists of LexicalEnvironemnt
, which in turn is made of three components:假设稍微简化一下(忽略 global/function/eval & no
let
和const
(即variable environment
)之间的差异), ExecutionContext
由LexicalEnvironemnt
组成,而 LexicalEnvironemnt 又由三个组件组成:
ExecutionContext
ExecutionContext
的引用this
variable references.this
变量引用的内容。 For unbound functions, this is set based on how the method is called ( 2 ) When a function is called a new ExecutionContext
is created for the duration of its execution (to track its variables as they change in Environment record , ...).当调用 function 时,会在其执行期间创建一个新的
ExecutionContext
(以跟踪其变量在环境记录中的变化,...)。
For normal function, sa b()
in example bellow, the creation of new ExecutionContext
is relatively simple.对于正常的 function,下面示例中的 sa
b()
,新ExecutionContext
的创建相对简单。
function a() {
var myVar = 42;
function b() {
console.log(myVar)
console.log(this)
}
b()
}
a()
EnvironmentContext
that's currently (ie of a()
) on execution stack ( 3 ).a()
)在执行堆栈 ( 3 ) 上的EnvironmentContext
。 This gives us access to the outer lexical scope variable myVar
.myVar
。function a() {
let myVar = 42;
function b() {
console.log(myVar) // from closure
console.log(myCVar) // will not be accessible, even if it will have lived in above frame (from c)
console.log(this)
}
return b
}
function c(f) {
let myVar = 48;
let myCVar = 49;
f()
}
returnedFun = a()
c(returnedFun)
In this case, when we run the method b
(as f()
within method c
, after being returned from a
), it is not so simple.在这种情况下,当我们运行方法
b
(作为方法c
中的f()
,从a
返回之后),事情就没那么简单了。 1)
and 3)
portions of the new ExecutionContext
are still populated the same, but 2)
has to be different. 1)
和3)
新ExecutionContext
的部分仍然填充相同,但2)
必须不同。
At the point where b
is returned from its lexical scope, ie from the function a
, a closure must be created out of current ExecutionContext
(the one for a()
being executed, with myVar: 42
in environment record ) and added to the returned function object b
.在
b
从其词法 scope 返回的位置,即从 function a
返回时,必须在当前ExecutionContext
中创建一个闭包(正在执行a()
的闭包,环境记录中有myVar: 42
)并添加到返回的function object b
。
When the function object is executed in function c
( f()
), instead of wiring the newly created ExecutionContext
's Reference to outer environment to the one on top of execution stack (ie the one for the currently executing c()
), the closure of the function object f
(returned function b
) must be used instead.当 function object 在 function
c
( f()
) 中执行时,不是将新创建的ExecutionContext
对外部环境的引用连接到执行堆栈顶部的那个(即当前正在执行的c()
的那个),必须使用 function object f
的关闭(返回 function b
)。
Ie the Reference to outer environment for the just being created ExecutionContext
of just executed f()
doesn't point to ExecutionContext
of the function that's currently running (ie runtime outer scope; would be of c()
) but to a captured closure of a no-longer-running lexically-outer-environment ( a()
).即,刚刚执行的
f()
的刚刚创建的ExecutionContext
对外部环境的引用不指向当前正在运行的 function 的ExecutionContext
(即运行时外部 scope;将是c()
),而是指向一个捕获的闭包不再运行词法外部环境( a()
)。
This captured closure is visible as?pseudo?这个捕获的闭包是可见的?伪? property when
console.dir
of the returnedFun
object ( .[[Scopes]][0].myVar == 42
). returnedFun
的console.dir
时的属性 object ( .[[Scopes]][0].myVar == 42
)。
let myObj = {asdf: 42}
function a() { console.write("tst");}
console.dir(a.bind(myObj))
Similarly, when bind
is used explicitely - the args/this is added to the function object, visible as?pseudo?同样,当
bind
被显式使用时 - args/this 被添加到 function object,可见为?伪? property [[BoundThis]]
.属性
[[BoundThis]]
。 And it's used, when the function object is invoked and the corresponding ExecutionContext
is created to populate its This binding .并且在调用 function object 并创建相应的
ExecutionContext
以填充其This 绑定时使用它。
But what about arrow functions?但是箭头函数呢? To the best of my googling, a common way to explain them is that they don't get their own
ExecutionContext
( 4 , 5 ) and instead re-use the one of their lexical outer-scope;根据我的谷歌搜索,解释它们的一种常见方法是它们没有获得自己的
ExecutionContext
( 4 , 5 ),而是重新使用它们的词法外部范围之一; but how does that work, really?但它是如何工作的,真的吗?
function a() {
let myVar = 42;
b = () => {
var myBVar = 48;
}
b()
console.log(myBVar) // not accessible -> run of b() must use copy of a's EC
}
a()
When the arrow function is executed in its lexical scope, it's - again - relatively straightforward.当箭头 function 在其词法 scope 中执行时,它再次相对简单。 When function
b()
is executed, the current ExecutionContext (for a
, which is b
's lexical outer scope) is duplicated (needs to be to allow having just its own variables, otherwise during a()
you could access myBVar
) and used;当 function
b()
被执行时,当前的 ExecutionContext(对于a
,这是b
的词法外部范围)被复制(需要允许只有它自己的变量,否则在a()
期间你可以访问myBVar
)并使用; including this binding (demonstated by explicit bind
ing example bellow).包括此绑定(由下面的显式
bind
示例演示)。
function a() {
console.log(this)
arrF = () => {
console.log(this.myMyObjVar)
}
arrF() // when called duplicates current ExecutionContext (LexicalEnvironment + thisBinding), runs in it.
}
var myObj = {myMyObjVar: 42}
a.bind(myObj)()
But what if the arrow function escapes its lexical scope?但是如果箭头 function 逃脱了它的词法 scope 呢? Ie it needs to have closure created?
即它需要创建闭包吗?
function a() {
console.log(this)
var asdf = 48;
arrF = () => {
console.log(this.myMyObjVar)
console.log(asdf)
}
return arrF
}
var myObj = {myMyObjVar: 42}
aBound = a.bind(myObj)
returnedArrF = aBound()
returnedArrF()
console.dir(returnedArrF)
In this case, returnedArrF
's closure needs to not only contain the Environment record of a()
's ExecutionContext
(to provide normal closure access to variables from outer lexical scope ( asdf
)), ie what Chromium Devtools show us as [[Scopes]]
, but also to its This binding .在这种情况下,
returnedArrF
的闭包不仅需要包含a()
的ExecutionContext
的环境记录(以提供对外部词法 scope ( asdf
) 变量的正常闭包访问),即 Chromium Devtools 向我们展示的[[Scopes]]
,还要对其This 进行绑定。 Ie needs to save pretty much the whole ExecutionContext
, to allow the excaped arrow function - when executed - to not need to have its own and reuse its outer lexical scope's one.即需要保存几乎整个
ExecutionContext
,以允许 excaped 箭头 function - 在执行时 - 不需要拥有自己的并重用其外部词法范围的箭头。
Curiously, the stored This binding doesn't seem to be surfaced as?pseudo?奇怪的是,存储的This 绑定似乎没有显示为?伪? property visible with
console.dir
, the same way as either bind
'ed this
or normal closure is.使用
console.dir
可见的属性,与bind
'ed this
或正常闭包的方式相同。
ExecutionContext
, specifically this binding for arrow functions, stored using similar mechanism (under similar model) as closure (think [[scopes]]
as chrome dev tools show them) is?ExecutionContext
的引用,特别是箭头函数的this 绑定,是否使用与闭包类似的机制(在类似模型下)存储(想想[[scopes]]
正如 chrome 开发工具显示的那样)是?thisBinding
created by bind(...)
and normal closures visible in via Chrome devtools/ console.dir
, but arrow function's this binding isn't?bind(...)
创建的thisBinding
和正常的闭包都在 via Chrome devtools/ console.dir
中可见,但箭头函数的this 绑定不是? Is it just implementation detail or is there some higher level reason?bind
'ed functions and arrow functions look when being inspected (or is it just implementation detail and not something JS model mandates?)?bind
的函数和箭头函数看起来有差异(或者它只是实现细节而不是 JS model 要求的东西?)? I understand that ExecutionContext
etc. is just a specification "model" and not how individual VMs (V8, ...) implement JS.我知道
ExecutionContext
等只是一个规范“模型”,而不是单个 VM(V8,...)如何实现 JS。 I also understand that Chromium devtools might show "pseudo" properties that don't really exist/are accessible on the objects (sa [[Scopes]]
).我还了解到 Chromium devtools 可能会显示对象上并不存在/可访问的“伪”属性 (sa
[[Scopes]]
)。
I'm also not interested in how arrow functions manifest, how to work with them (I think I have decent grasp; but if you think I missed something based on my examples - feel free to tell me).我也对箭头函数如何表现、如何使用它们不感兴趣(我认为我掌握得很好;但如果你认为我根据我的示例遗漏了一些东西 - 请随时告诉我)。
Instead, I'm curious how the specification "model" maps to actual implementation.相反,我很好奇规范“模型”如何映射到实际实现。 I hope it's clear from the questions.
我希望从问题中可以清楚地知道。
Things I tried to read to make sense of this:我试图阅读以理解这一点的东西:
This question is sprawling and there is a lot of confusion presented but I hope this post can provide some clarity.这个问题很广泛,并且存在很多混乱,但我希望这篇文章可以提供一些清晰的信息。 This is a community wiki post that can be edited by anyone.
这是一个社区 wiki 帖子,任何人都可以编辑。 If you have follow-up questions, we are happy to help.
如果您有后续问题,我们很乐意为您提供帮助。
non-arrow functions非箭头函数
A function's lexical environment is defined at the time the function is created .函数的词法环境是在创建function 时定义的。 A function is created when the evaluator encounters one during ordinary evaluation.
当评估者在普通评估中遇到一个 function 时,将创建一个 function。 This is true for non-arrow and arrow functions alike.
这对于非箭头和箭头函数都是如此。 However, all non-arrow functions have a dynamic context,
this
, that changes based on how a function is called or bind
ed, call
ed, or apply
ed -但是,所有非箭头函数都有一个动态上下文
this
,它会根据 function 的调用方式或bind
方式、 call
方式或apply
方式而变化 -
function F(...args) { console.log(this.value, ...args) } value = "global" const A = F.bind({value: "hello"}, 1, 2, 3) const B = A.bind({value: "world"}, 4, 5, 6) const o = { value: "object", method: F } F(7,8,9) // global 7 8 9 A(7,8,9) // hello 1 2 3 7 8 9 B(7,8,9) // hello 1 2 3 4 5 6 7 8 9 o.method(7,8,9) // object 7 8 9
arrow functions箭头函数
What happens when we change F
to an arrow function?当我们将
F
更改为箭头 function 时会发生什么? Arrow functions have a lexical this
which cannot be rebound.箭头函数有一个无法反弹的词法
this
。 But why?但为什么?
const F = (...args) => {
console.log(this.value, ...args)
}
That is equivalent to the code below -这相当于下面的代码 -
const LEXICAL_THIS = this
function F(...args) {
console.log(LEXICAL_THIS.value, ...args)
}
Instead of having their own dynamic context, arrow functions inherit this
from the environment they are created in. And because they have no dynamic context of their own, there exists no context to bind
.箭头函数没有自己的动态上下文,而是从创建它们的环境中继承
this
。而且因为它们没有自己的动态上下文,所以不存在要bind
的上下文。 Here's how it behaves -这是它的行为方式 -
const F = (...args) => { console.log(this.value, ...args) } value = "global" const A = F.bind({value: "hello"}, 1, 2, 3) const B = A.bind({value: "world"}, 4, 5, 6) const o = { value: "object", method: F } F(7,8,9) // global 7 8 9 A(7,8,9) // global 1 2 3 7 8 9 B(7,8,9) // global 1 2 3 4 5 6 7 8 9 o.method(7,8,9) // global 7 8 9
nested functions嵌套函数
If you write a function inside of another function, the inner function is not created until the outer function is executed. If you write a function inside of another function, the inner function is not created until the outer function is executed. Executing the outer function multiple times will recreate the inner function (and environment) multiple times -
多次执行外部 function 将多次重新创建内部 function (和环境) -
const add = x => y => x + y const add3 = add(3) const add6 = add(6) console.log(add3(10), add3(20), add3(30)) // 13 23 33 console.log(add6(10), add6(20), add6(30)) // 16 26 36
Here's the same program using non-arrow functions -这是使用非箭头函数的相同程序 -
function add(x) { return function(y) { return x + y } } const add3 = add(3) const add6 = add(6) console.log(add3(10), add3(20), add3(30)) // 13 23 33 console.log(add6(10), add6(20), add6(30)) // 16 26 36
TLDR: arrow functions... interact with ExecutionContext/environment and closures just like a block of code. TLDR:箭头函数...与 ExecutionContext/环境和闭包交互,就像代码块一样。
You overcomplicate the things here, in my opinion, I think it is actually only about this
keyword, as it is the only thing in the ExecutionContext
that differentiate arrow functions from traditional ones .您在这里使事情过于复杂,在我看来,我认为它实际上只是关于
this
关键字,因为它是ExecutionContext
中唯一将箭头函数与传统函数区分开来的东西。
The questions 1 and 2 make no sense once you have the answer for 3, ie the "model" of arrow functions, which is quite simple if considering that they don't have this
binding.一旦你得到了 3 的答案,即箭头函数的“模型”,问题 1 和 2 就没有意义了,如果考虑到它们没有
this
绑定,这很简单。
Consider the examples below:考虑以下示例:
const fn = () => { console.log(this); };
// block code call:
{ console.log(this); }
// arrow function call:
fn();
// arrow function call with `this` value:
fn.call(0);
// arrow function call as method:
const object = {fn};
object.fn();
No matter in which context the above examples are executed, they will log identical values.无论在哪个上下文中执行上述示例,它们都会记录相同的值。
Thus the "model" of an arrow function can be defined as: a reusable block of code that can be executed like a function, with arguments acting as block scoped variables.因此,箭头 function 的“模型”可以定义为:可以像 function 一样执行的可重用代码块,其中 arguments 充当块范围的变量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.