繁体   English   中英

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

[英]Functions, arrow functions, closures and ExecutionContext

我试图了解 JavaScript 中的箭头函数,并且有一些关于它们如何与 ExecutionContext/environment 和闭包交互的问题。

我如何理解model:

据我所知,JS 中的“规范”model 是当代码被执行时,会维护一个ExecutionContext堆栈( 16 )。 即在开始时有一个全局的ExecutionContext ,当 function 被调用时,一个新的ExecutionContext被添加到它的执行时间,当它完成时,它被弹出。 即它匹配callstack 上的帧

假设稍微简化一下(忽略 global/function/eval & no letconst (即variable environment )之间的差异), ExecutionContextLexicalEnvironemnt组成,而 LexicalEnvironemnt 又由三个组件组成:

  1. 环境记录:变量/函数符号和它们所代表的对象之间的映射。
  2. 对外部环境的引用:对词法外部ExecutionContext的引用
  3. 此绑定this变量引用的内容。 对于未绑定函数,这是根据调用方法的方式设置的 ( 2 )

当调用 function 时,会在其执行期间创建一个新的ExecutionContext (以跟踪其变量在环境记录中的变化,...)。

正常功能

正常 function,在词法 scope 内:

对于正常的 function,下面示例中的 sa b() ,新ExecutionContext的创建相对简单。

function a() {
 var myVar = 42;
 function b() {
   console.log(myVar)
   console.log(this)
 }
 b()
}

a()
  1. 环境记录:对所有类型总是很简单,扫描方法,记下所有符号,初始化为默认值即可。
  2. 对外部环境的引用:我们在其词法外部 scope 中运行该方法,即我们可以简单地引用当前(即a() )在执行堆栈 ( 3 ) 上的EnvironmentContext 这使我们能够访问外部词法 scope 变量myVar
  3. 它被正常调用,所以我们将 go 与全局绑定,即在浏览器中为 window。
正常 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)

在这种情况下,当我们运行方法b (作为方法c中的f() ,从a返回之后),事情就没那么简单了。 1)3)ExecutionContext的部分仍然填充相同,但2)必须不同。

b从其词法 scope 返回的位置,即从 function a返回时,必须在当前ExecutionContext中创建一个闭包(正在执行a()的闭包,环境记录中有myVar: 42 )并添加到返回的function object b

当 function object 在 function c ( f() ) 中执行时,不是将新创建的ExecutionContext对外部环境的引用连接到执行堆栈顶部的那个(即当前正在执行的c()的那个),必须使用 function object f的关闭(返回 function b )。

即,刚刚执行的f()的刚刚创建的ExecutionContext对外部环境的引用不指向当前正在运行的 function 的ExecutionContext (即运行时外部 scope;将是c() ),而是指向一个捕获的闭包不再运行词法外部环境( a() )。

这个捕获的闭包是可见的?伪? returnedFunconsole.dir时的属性 object ( .[[Scopes]][0].myVar == 42 )。

正常function,绑定
let myObj = {asdf: 42}
function a() { console.write("tst");}
console.dir(a.bind(myObj))

同样,当bind被显式使用时 - args/this 被添加到 function object,可见为?伪? 属性[[BoundThis]] 并且在调用 function object 并创建相应的ExecutionContext以填充其This 绑定时使用它。

箭头函数

但是箭头函数呢? 根据我的谷歌搜索,解释它们的一种常见方法是它们没有获得自己的ExecutionContext ( 4 , 5 ),而是重新使用它们的词法外部范围之一; 但它是如何工作的,真的吗?

箭头函数,在词法 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()

当箭头 function 在其词法 scope 中执行时,它再次相对简单。 当 function b()被执行时,当前的 ExecutionContext(对于a ,这是b的词法外部范围)被复制(需要允许只有它自己的变量,否则在a()期间你可以访问myBVar )并使用; 包括此绑定(由下面的显式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)()
箭头函数,词法外部 scope

但是如果箭头 function 逃脱了它的词法 scope 呢? 即它需要创建闭包吗?

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)

在这种情况下, returnedArrF的闭包不仅需要包含a()ExecutionContext环境记录(以提供对外部词法 scope ( asdf ) 变量的正常闭包访问),即 Chromium Devtools 向我们展示的[[Scopes]] ,还要对其This 进行绑定 即需要保存几乎整个ExecutionContext ,以允许 excaped 箭头 function - 在执行时 - 不需要拥有自己的并重用其外部词法范围的箭头。

奇怪的是,存储的This 绑定似乎没有显示为?伪? 使用console.dir可见的属性,与bind 'ed this或正常闭包的方式相同。

我有什么问题?

  1. 对外部词法上下文的ExecutionContext的引用,特别是箭头函数的this 绑定,是否使用与闭包类似的机制(在类似模型下)存储(想想[[scopes]]正如 chrome 开发工具显示的那样)是?
  • 如果是这样,为什么bind(...)创建的thisBinding和正常的闭包都在 via Chrome devtools/ console.dir中可见,但箭头函数的this 绑定不是? 它只是实现细节还是有一些更高层次的原因?
  1. 为什么在检查时显式bind的函数和箭头函数看起来有差异(或者它只是实现细节而不是 JS model 要求的东西?)?
  2. 我的 model 对吗?
什么不是我的问题/笔记?

我知道ExecutionContext等只是一个规范“模型”,而不是单个 VM(V8,...)如何实现 JS。 我还了解到 Chromium devtools 可能会显示对象上并不存在/可访问的“伪”属性 (sa [[Scopes]] )。

我也对箭头函数如何表现、如何使用它们不感兴趣(我认为我掌握得很好;但如果你认为我根据我的示例遗漏了一些东西 - 请随时告诉我)。

相反,我很好奇规范“模型”如何映射到实际实现。 我希望从问题中可以清楚地知道。

笔记:

我试图阅读以理解这一点的东西:

这个问题很广泛,并且存在很多混乱,但我希望这篇文章可以提供一些清晰的信息。 这是一个社区 wiki 帖子,任何人都可以编辑。 如果您有后续问题,我们很乐意为您提供帮助。

非箭头函数

函数的词法环境是在创建function 时定义的。 当评估者在普通评估中遇到一个 function 时,将创建一个 function。 这对于非箭头和箭头函数都是如此。 但是,所有非箭头函数都有一个动态上下文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

箭头函数

当我们将F更改为箭头 function 时会发生什么? 箭头函数有一个无法反弹的词法this 但为什么?

const F = (...args) => {
  console.log(this.value, ...args)
}

这相当于下面的代码 -

const LEXICAL_THIS = this
function F(...args) {
   console.log(LEXICAL_THIS.value, ...args)
}

箭头函数没有自己的动态上下文,而是从创建它们的环境中继承this 。而且因为它们没有自己的动态上下文,所以不存在要bind的上下文。 这是它的行为方式 -

 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

嵌套函数

If you write a function inside of another function, the inner function is not created until the outer function is executed. 多次执行外部 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

这是使用非箭头函数的相同程序 -

 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:箭头函数...与 ExecutionContext/环境和闭包交互,就像代码块一样。

您在这里使事情过于复杂,在我看来,我认为它实际上只是关于this关键字,因为它是ExecutionContext中唯一将箭头函数传统函数区分开来的东西
一旦你得到了 3 的答案,即箭头函数的“模型”,问题 1 和 2 就没有意义了,如果考虑到它们没有this绑定,这很简单。
考虑以下示例:

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();

无论在哪个上下文中执行上述示例,它们都会记录相同的值。
因此,箭头 function 的“模型”可以定义为:可以像 function 一样执行的可重用代码块,其中 arguments 充当块范围的变量。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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