简体   繁体   English

函数、箭头函数、闭包和 ExecutionContext

[英]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 和闭包交互的问题。

How I understand the model:我如何理解model:

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堆栈( 16 )。 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 letconst (即variable environment )之间的差异), ExecutionContextLexicalEnvironemnt组成,而 LexicalEnvironemnt 又由三个组件组成:

  1. Environment record : mapping between variable/func symbols and objects they represent.环境记录:变量/函数符号和它们所代表的对象之间的映射。
  2. Reference to outer environment : Ref to lexically outer ExecutionContext对外部环境的引用:对词法外部ExecutionContext的引用
  3. This binding : what this variable references.此绑定this变量引用的内容。 For unbound functions, this is set based on how the method is called ( 2 )对于未绑定函数,这是根据调用方法的方式设置的 ( 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 (以跟踪其变量在环境记录中的变化,...)。

Normal functions正常功能

Normal function, within lexical scope:正常 function,在词法 scope 内:

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()
  1. Environment record: It's always simple for all types, just scan the method, note all symbols, init to default.环境记录:对所有类型总是很简单,扫描方法,记下所有符号,初始化为默认值即可。
  2. Reference to outer environment: We're running the method within its lexical outer scope, ie we can simply reference the EnvironmentContext that's currently (ie of a() ) on execution stack ( 3 ).对外部环境的引用:我们在其词法外部 scope 中运行该方法,即我们可以简单地引用当前(即a() )在执行堆栈 ( 3 ) 上的EnvironmentContext This gives us access to the outer lexical scope variable myVar .这使我们能够访问外部词法 scope 变量myVar
  3. It's called normally, so we'd go with global binding for this, ie in browser a window.它被正常调用,所以我们将 go 与全局绑定,即在浏览器中为 window。
Normal function, outside lexical scope:正常 function,外部词汇 scope:
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 ). returnedFunconsole.dir时的属性 object ( .[[Scopes]][0].myVar == 42 )。

Normal function, bound正常function,绑定
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 绑定时使用它。

Arrow functions箭头函数

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?但它是如何工作的,真的吗?

Arrow functions, within lexical scope:箭头函数,在词法 scope 内:
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)()
Arrow functions, outside lexical scope箭头函数,词法外部 scope

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或正常闭包的方式相同。

What are my questions?我有什么问题?

  1. Are the references to outer lexical context's 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 开发工具显示的那样)是?
  • If that's the case, why are both 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?它只是实现细节还是有一些更高层次的原因?
  1. Why are there differences in how explicitely 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 要求的东西?)?
  2. Do I have the model right?我的 model 对吗?
What is not my question / notes?什么不是我的问题/笔记?

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.我希望从问题中可以清楚地知道。

Notes:笔记:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM