【问题标题】:Functions, arrow functions, closures and ExecutionContext函数、箭头函数、闭包和 ExecutionContext
【发布时间】:2022-08-20 23:04:59
【问题描述】:

我试图理解 JavaScript 中的 arrow functions 并且对它们如何与 ExecutionContext/environment 和闭包交互有一些疑问。

我如何理解模型:

据我所知,JS 中的“规范”模型是,随着代码的执行,会维护ExecutionContexts 的堆栈(16)。 IE。一开始有一个全局的ExecutionContext,当一个函数被调用时,一个新的ExecutionContext在它的执行时被添加,当它完成时,它被弹出。 IE。它匹配frames on callstack

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

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

    当一个函数被调用时,一个新的ExecutionContext 在其执行期间被创建(跟踪它的变量,因为它们在环境记录, ...)。

    正常功能

    正常功能,在词法范围内:

    对于正常功能,s.a. b() 在下面的例子中,创建新的ExecutionContext 比较简单。

    function a() {
     var myVar = 42;
     function b() {
       console.log(myVar)
       console.log(this)
     }
     b()
    }
    
    a()
    
    1. 环境记录:对所有类型来说总是很简单,只需扫描方法,注意所有符号,初始化为默认值。
    2. 对外部环境的引用:我们在其词法外部范围内运行该方法,即我们可以简单地引用执行堆栈 (3) 上当前的 EnvironmentContext(即 a()) .这使我们可以访问外部词法范围变量myVar
    3. 它被正常调用,所以我们会为此使用全局绑定,即在浏览器中的一个窗口。
      正常功能,超出词法范围:
      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返回后),就没有那么简单了。新的ExecutionContext1)3) 部分仍然填充相同,但2) 必须不同。

      b 从其词法范围返回时,即从函数 a,必须在当前 ExecutionContext 之外创建一个闭包(a() 正在执行,myVar: 42环境记录) 并添加到返回的函数对象b

      当函数对象在函数cf())中执行时,而不是连线新创建的ExecutionContext\'s参考外部环境对于执行堆栈顶部的那个(即当前执行的c()),必须使用函数对象f(返回函数b)的闭包。

      IE。这参考外部环境对于刚刚创建的ExecutionContext 刚刚执行的f() 不指向当前正在运行的函数的ExecutionContext(即运行时外部范围;将是c()),而是指向捕获的闭包不再运行的词法外部环境 (a())。

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

      正常功能,有界
      let myObj = {asdf: 42}
      function a() { console.write(\"tst\");}
      console.dir(a.bind(myObj))
      

      同样,当显式使用bind 时 - args/this 被添加到函数对象中,以 ?pseudo?财产[[BoundThis]]。当函数对象被调用并创建相应的ExecutionContext 以填充其这个绑定.

      箭头函数

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

      词法范围内的箭头函数:
      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()
      

      当箭头函数在其词法范围内执行时,它 - 再次 - 相对简单。当函数b()被执行时,当前的ExecutionContext(对于a,这是b\的词法外部范围)被复制(需要允许只有它自己的变量,否则在a()你可以访问myBVar) 并使用;包含这个绑定(由下面明确的binding 示例演示)。

      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)()
      
      箭头函数,超出词法范围

      但是如果箭头函数超出了它的词法范围呢? IE。它需要创建闭包吗?

      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()\'s ExecutionContext (提供对来自外部词法范围 (asdf) 的变量的正常闭包访问),即 Chromium Devtools 向我们展示的 [[Scopes]],但也对其这个绑定.即需要保存几乎整个 ExecutionContext,以允许 excaped 箭头函数 - 在执行时 - 不需要拥有自己的并重用其外部词法范围。

      奇怪的是,存储这个绑定似乎没有作为?伪浮出水面? console.dir 可见的属性,与bind\'ed this 或正常关闭的方式相同。

      我有什么问题?

      1. 是对外部词汇上下文ExecutionContext的引用,具体来说这个绑定对于箭头函数,使用与闭包类似的机制(在类似模型下)存储(想想[[scopes]],就像 chrome 开发工具显示的那样)是?
        • 如果是这种情况,为什么thisBindingbind(...) 创建,正常闭包在Chrome devtools/console.dir 中可见,但箭头函数这个绑定不是吗?它只是实现细节还是有一些更高层次的原因?
        1. 为什么在检查时bind\'ed 函数和箭头函数的外观会存在差异(或者它只是实现细节而不是 JS 模型要求的内容?)?
        2. 我的模型对吗?
        什么不是我的问题/笔记?

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

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

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

        笔记:

        我试图阅读以理解这一点的内容:

  • \"我的问题是什么?\"一个明确的迹象表明这个问题\"需要更多的关注\"
  • \"一种常见的解释方式是 [箭头函数] 没有自己的 ExecutionContext\" - 不,这是错误的,并且不受您提供的引用的支持。箭头函数在调用它们时通常创建的执行上下文中不会得到自己的 [[ThisBinding]]。
  • \"检查时显式绑定函数和箭头函数的外观差异\" - 这绝对是您正在使用的调试器的实现细节。它不是由任何东西强制要求的(当然,它或多或少地受到规范中描述的心理模型的影响)。
  • 顺便说一句,你的心智模型\"正常功能,在词法范围内\" 太复杂了。这已经被一般的\"任何范围内的正常函数\"案例所涵盖。\"b 从其词法范围返回时,必须创建一个闭包\" 是错误的。在定义(创建)函数时已经正确创建了闭包,因为每一个函数,无论以后该函数会发生什么。

标签: javascript closures executioncontext


【解决方案1】:

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

非箭头函数

函数的词法环境是在函数被定义时定义的创建.当评估者在普通评估中遇到一个函数时,就会创建一个函数。这对于非箭头和箭头函数都是如此。但是,所有非箭头函数都有一个动态上下文 this,它会根据函数的调用方式或 binded、called 或 applyed 而变化 -

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 更改为箭头函数时会发生什么?箭头函数有词汇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

嵌套函数

如果您在另一个函数内部编写一个函数,则在执行外部函数之前不会创建内部函数。多次执行外部函数将多次重新创建内部函数(和环境) -

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

【讨论】:

    【解决方案2】:

    TLDR:箭头函数...与 ExecutionContext/环境和闭包交互就像一段代码。

    你把这里的事情复杂化了,在我看来,我认为它实际上只是关于this 关键字,因为它是ExecutionContext 中唯一将arrow functionstraditional ones 区分开来的东西。
    一旦你有了 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();
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-31
      • 2018-10-18
      • 1970-01-01
      • 2020-05-07
      • 2017-10-02
      相关资源
      最近更新 更多