脚本范围的目的是什么?

met*_*ask 5 javascript scope let ecmascript-6

在DevTools控制台中检查函数的范围时,我注意到了一个"脚本"范围.一些研究之后,它似乎是要创建letconst变量.

没有constlet变量的脚本中函数的作用域:

全球范围

带有let变量的脚本中函数的作用域:

全局范围和脚本范围

然而,1控制台中的以下打印- 脚本范围中的变量仍然可以从其他脚本访问:

<script>let v = 1</script>
<script>console.log(v)</script>
Run Code Online (Sandbox Code Playgroud)

我听说过ES6模块,其中顶层变量无法从模块外部访问.这是范围用于什么或它有任何其他目的?

T.J*_*der 12

JavaScript 没有“脚本作用域”。\xc2\xb9 您所看到的正是 Google V8 JavaScript 引擎所称的全局环境的一部分,该部分保存了所创建的新样式的词法作用域全局变量当您在全局范围内使用letconst、 和时。class它们仍然是全局变量,但它们与全局范围内的函数声明创建的旧式全局变量不同var(V8 在Global下面显示[[Scopes]])。V8 调试器在这两个不同的位置列出了两种类型的全局变量。

\n

如果您愿意,可以在这里停止阅读,但如果您想了解具体细节,请继续阅读。:-)

\n

那么为什么全球环境有两个全球部分呢?一句话:历史。

\n

JavaScript 的原始形式的全局变量(全局变量作用域绑定\xc2\xb2)存在多个问题。主要的两个是:

\n
    \n
  • 它们不仅仅是全局可用的标识符,它们还是全局对象的属性(this在全局范围内,也可以通过window浏览器上的全局或规范定义的较新的globalThis全局来访问)。这意味着您可以在全局对象中查找您不知道名称的内容(通过使用for-inObject.keys或类似的方法)。
  • \n
  • 对同一标识符的重复声明不是错误。
  • \n
\n

除了全局范围内的这些问题之外,var还有一个问题是它没有块范围;块中的函数声明(也创建 var 范围的绑定)未指定,但允许作为扩展,导致它们在 JavaScript 实现中的语义很大程度上不兼容。

\n

当需要添加一种新的方式来声明具有更好语义的事物时(let, const, class; “词法范围的绑定”),推动 JavaScript 发展的委员会 (ECMA TC39) 必须弄清楚这些新语义如何在全局范围内工作。他们的解决方案是将全局环境分为两部分\xc2\xa0\xe2\x80\x94,一部分用于旧样式,另一部分用于新样式\xc2\xa0\xe2\x80\x94,但仍然“逻辑上”对待它作为单一环境。从规格来看

\n
\n

全局环境记录在逻辑上是单个记录,但它被指定为封装对象环境记录声明性环境记录的复合体。

\n
\n

“环境记录”是一个概念对象,它保存绑定\xc2\xb2(变量等)和其他一些东西。将其与您在屏幕截图中看到的内容结合起来:

\n
    \n
  • “对象环境记录”是使用全局对象的属性进行 var 范围绑定的记录。这就是 V8Global下所说的[[Scopes]]
  • \n
  • “声明性环境记录”是保存词法范围绑定的记录(直接保存,而不是在单独的对象中)。这就是 V8Script下所说的[[Scopes]]
  • \n
\n

在你的屏幕截图中,你有let f,它创建了一个名为 的词法范围绑定"f",因此 V8 在 下显示了它[[Scopes]].Script。如果您var f这样做,V8 会在 下显示该内容[[Scopes]].Global。但同样,两者都是全局的。

\n
\n

当他们说全球环境的两个部分“逻辑上”是一个记录时,这是什么意思?基本上,它们意味着它不仅仅是两个嵌套环境(尽管在很多方面它的行为就像它本来的样子),只有一个全局范围(即使环境有两个部分与其相关)。您可以看到的一种方法是,您不能在全局范围内同时使用var和来声明某些内容let,这是一个错误:

\n

\r\n
\r\n
var a = 1;\nlet a = 2; // SyntaxError: Identifier \'a\' has already been declared
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

如果它们只是嵌套环境,那么您可以这样做\xc2\xa0\xe2\x80\x94,但这将是多么令人困惑!

\n

但是,虽然它们不仅仅是嵌套的,但它们确实是嵌套的。您可以通过创建 var 作用域的全局而不使用var(通过分配给全局对象上的属性)来证明这一点:

\n

\r\n
\r\n
window.a = "var-scoped a";\nlet a = "lexically-scoped a";\nconsole.log(a);         // "lexically-scoped a"\nconsole.log(window.a);  // "var-scoped a"\n\nlet b = "lexically-scoped b";\nwindow.b = "var-scoped b";\nconsole.log(b);         // "lexically-scoped b"\nconsole.log(window.b);  // "var-scoped b"
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

也许不言而喻,您不应该故意这样做,但它演示了双重环境的嵌套方面。

\n
\n

\xc2\xb9 它确实具有模块范围,这是不同的,但是像您这样的非模块脚本中的顶级代码是在全局范围内执行的。

\n

\xc2\xb2绑定是名称(如a)和其当前值的存储槽的组合。变量是绑定。常量、参数、由函数声明创建的变量以及各种内置的东西(例如this.

\n


Mic*_*ski 8

当您var在顶层声明变量(即不在函数内)时,它会自动变为全局变量(因此在浏览器中您可以将其作为属性访问window).它是用声明的变量不同letconst-他们不会成为全局变量.您可以在另一个脚本标记中访问它们,但不能将它们作为属性访问它们window.

看这个例子:

<script>
  var test1 = 42;
  let test2 = 43;
</script>
<script>
  console.log(test1); // 42
  console.log(window.test1); // 42
  console.log(test2); // 43
  console.log(window.test2); // undefined
</script>
Run Code Online (Sandbox Code Playgroud)

  • @samson这取决于你如何定义"全局".在JavaScript中,"global"通常意味着"可以从任何地方访问*和*全局对象的属性(浏览器中的`window`,Node.js中的`global`)".如果你将它定义为"可以从任何地方访问",那么是 - 在顶层使用`let`定义的变量是全局的. (2认同)