let 和 const 如何适应当前的执行上下文?他们是不是每次都创建一个新的?

Posted

技术标签:

【中文标题】let 和 const 如何适应当前的执行上下文?他们是不是每次都创建一个新的?【英文标题】:How do let and const fit in the current execution context? Do they create a new one every time?let 和 const 如何适应当前的执行上下文?他们是否每次都创建一个新的? 【发布时间】:2020-08-09 08:42:29 【问题描述】:

所以,我一直在阅读一些关于闭包、作用域、词法环境和执行上下文的内容。 据我了解,当前的执行上下文有一个词法环境和一个变量环境(这似乎是一种特定类型的词法环境,从堆栈溢出时读取其他线程,我发现在 ECMAScript 2016 中提到但未定义)。变量环境具有用 var 声明的标识符以及与它们关联的值。词法环境在其记录的某处具有使用 let 和 const 声明的变量的绑定和值。不过,我有一些与此相关的问题。

变量环境只是词法环境中的变量环境记录(并且由于某种原因记录部分已经脱落)还是一个单独的词法环境具有外部词法环境对某物的引用(外部变量环境或外部词汇环境)?

另外,闭包和作用域在这张图片中的位置是什么?据我了解,范围只是当前执行上下文可访问的内容,但它只是可访问内容的想法还是范围的定义还包括 javascript 搜索变量的方式,查看当前的词法环境和变量环境以及范围链上的后续环境?关于闭包,MDN 将闭包定义为“捆绑在一起(封闭)的函数与对其周围状态(词法环境)的引用的组合”,我看到它在 ECMAScript 2016 中多次使用 - 闭包只是新的开始执行上下文,因为它们决定了词法环境(变量环境呢)并且它们“包含”函数?

最后,带有 let 和 const 的变量在当前执行上下文中存储在哪里?我在一些 Stack Overflow 线程中看到它们处于词法环境中(通过在 Google 中搜索,我找不到任何东西) - 具体来说,这里的第一个答案 (Where are global let variables stored?) 说“因为你使用了 let 而不是 var,它存储在与全局词法环境对象相关的声明性环境记录中,而不是存储在全局对象本身上。”我认为总的来说是一样的。这种情况怎么办:

function foo() 
    let a = 1;
    
        let a = 2;
        console.log(a)
    
    console.log(a)


foo()

这里发生了什么——名为“a”的两个变量不能在同一个词法环境中,对吧?所以有两个词法环境,但是第二个是如何创建的。是否在新块的开头创建了新的执行上下文?是每个块都会发生这种情况,还是只有那些使用 let 和 const 声明了新变量的块?

抱歉所有问题,但我的主要问题是最后一个,我不明白理解它的基础,这就是我问所有问题的原因。感谢您的宝贵时间。

【问题讨论】:

【参考方案1】:

这里有几个问题,真的。

MDN 将closure 定义为:

闭包是捆绑在一起(封闭)的函数与对其周围状态(词法环境)的引用的组合。换句话说,闭包让你可以从内部函数访问外部函数的作用域。在 JavaScript 中,每次创建函数时都会在创建函数时创建闭包。

希望这里的“换句话说”部分对“词汇环境”的含义有所帮助。

相比之下,letconst 的作用域是块。来自MDN:

let 允许您声明限制在块语句或使用它的表达式的范围内的变量,这与 var 关键字不同,它在全局范围内定义变量,或者在本地定义整个函数,而不考虑块范围。 ...

所以基本上是一样的,只是作用域是块级而不是函数级。

“全局词法环境对象”和“全局对象”的区别

全局对象基本上意味着全局变量作为属性添加到window 对象。请注意此示例中的差异(假设此代码在全局范围内运行,而不是在函数中):

let t = 4;
console.log(t, window.t); // window.t will be undefined
// --> 4, undefined

var q = 4;
console.log(q, window.q); // window.q will equal the global q
// --> 4, 4

名为“a”的两个变量不能在同一个词法环境中?

*编辑:在阅读了更多内容后,我发现我认为我对词汇环境的了解并不是很准确。查看ecma-262 specs,这似乎对这个主题有所了解:

一个词法环境由一个环境记录和一个对外部词法环境的可能为空的引用组成。通常,词法环境与 ECMAScript 代码的某些特定句法结构相关联,例如 FunctionDeclaration、BlockStatement 或 TryStatement 的 Catch 子句,并且每次评估此类代码时都会创建一个新的词法环境。

因此,您的示例中的块似乎相对肯定地获得了自己的词法环境,具有指向函数体外部词法环境的引用/指针,等等。

ecma specs 中关于letconst 的部分也做了一个有趣的区分:

变量在实例化包含它们的词法环境时创建,但在评估变量的词法绑定之前不得以任何方式访问。

可变环境

查看spec section on Execution Contexts,可以看出Execution Context包含了一个叫VariableEnvironment的组件:

标识其 EnvironmentRecord 保存由此执行上下文中的 VariableStatements 创建的绑定的词法环境。

所以它是第二个词法环境,它有一个特定的目的。要弄清楚这个特殊用途是什么,让我们看看Environment Record 是什么:

声明性环境记录用于定义 ECMAScript 语言句法元素的效果,例如 FunctionDeclarations、VariableDeclarations 和 Catch 子句,它们直接将标识符绑定与 ECMAScript 语言值相关联。对象环境记录用于定义 ECMAScript 元素的效果,例如 WithStatement 将标识符绑定与某些对象的属性相关联。

似乎有一些东西跳出来了。它讨论了catch 子句和with 语句。

Catch 子句似乎是一个奇怪的例外,在规范附件B.3.5 VariableStatements in Catch blocks 中进行了讨论。这讨论了对变量声明行为的修改。 With 语句也很奇怪,因为它们允许您引用对象的属性,就好像它们是局部变量一样。

我在这里猜测了一下,但在我看来,执行上下文的 VariableEnvironment 属性是第二个词法环境,旨在处理类似于局部变量但实际上并非如此的奇怪事物。

【讨论】:

感谢您的广泛回答。然而,我无法完全理解这个类比。词法环境包含对外部词法环境的引用和环境记录,对吗?这种对外部词法环境的引用的存在创建了一个范围链,它是一种“嵌套”或“分级”。你是说词汇环境里面也有层次吗?在我的示例中,有一个全局词法环境和 foo 链接的环境。 foo 这里是否有两个“级别”,内层用 表示? 或者您在地址类比中谈论范围链接?因为,据我了解,这需要单独的词汇环境。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 中词法环境的使用似乎表明在大括号内创建了一个新的词法环境(至少有时如此):但是,重要的是要指出嵌套在 case 中的块子句将创建一个新的块范围词法环境,它不会产生上面显示的重新声明错误。在同一行中,if 块的 foo 已经在词法环境中创建,但尚未达到(并终止)其初始化(这是语句本身的一部分)。 在这张图片中:pasteboard.co/J5vDnSe.png 1 是全局词法环境,2 是 foo 使用的词法环境,但三是什么?它只是 2 的一部分还是引用 2 的新词法环境? @asharpharp:编辑了我的答案。希望现在好多了。

以上是关于let 和 const 如何适应当前的执行上下文?他们是不是每次都创建一个新的?的主要内容,如果未能解决你的问题,请参考以下文章

JS变量声明提升

ECAMScript中的let和const

Javascript 1.5/1.6 新特性

笔记:原始值与引用值执行上下文与作用域垃圾回收

ES6学习 第一章 let 和 const 命令

ES6 let和const