脚本范围的目的是什么?
What is the purpose of the script scope?
在 DevTools 控制台中检查函数的作用域时,我注意到一个 "script" 作用域。经过一些研究,它似乎是为 let
和 const
变量创建的。
没有 const
或 let
变量的脚本中函数的范围:
具有 let
变量的脚本中函数的作用域:
然而在控制台中打印了以下内容 1
- 脚本作用域中的变量仍然可以从其他脚本访问:
<script>let v = 1</script>
<script>console.log(v)</script>
我听说过 ES6 模块中的顶级变量无法从模块外部访问。这是示波器的用途还是有其他用途?
当您在顶层(即不在函数内部)使用 var
声明一个变量时,它会自动成为一个全局变量(因此在浏览器中您可以将其作为 属性 window
)。它与使用 let
和 const
声明的变量不同——它们不会成为全局变量。您可以在另一个脚本标记中访问它们,但不能将它们作为 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>
JavaScript 没有“脚本作用域”。¹您所看到的正是 Google 的 V8 JavaScript 引擎所称的全局环境部分它包含在全局范围内使用 let
、const
和 class
时创建的词法范围全局变量的新样式。它们仍然是全局变量,但它们不同于由 var
创建的旧式全局变量和全局范围内的函数声明(V8 在 Global
下的 [[Scopes]]
中显示)。 V8 调试器在这两个不同的地方列出了两种类型的全局变量。
如果你愿意,你可以在这里停止阅读,但如果你想要了解细节,请继续阅读。 :-)
那么为什么全球环境有两个全球部分?一句话:历史。
JavaScript 的原始形式的全局变量(global var-scoped bindings²)有多个问题。主要的两个是:
- 它们不仅仅是全局可用的标识符,它们也是全局对象的属性(
this
在全局范围内,也可以通过浏览器上的 window
全局或较新的 globalThis
规范定义的全局)。这意味着您可以在全局对象中查找您不知道其名称的内容(通过使用 for-in
、Object.keys
或类似的方法)。
- 对同一标识符的重复声明不是错误。
除了全局范围内的那些问题之外,var
还存在没有块范围的问题;和块中的函数声明(也创建 var-scoped 绑定)未指定但允许作为扩展,导致它们在 JavaScript 实现中很大程度上不兼容的语义。
当需要添加一种声明具有更好语义的事物的新方法时(let
、const
、class
;“词法范围绑定”),委员会认为moves JavaScript forward (ECMA TC39) 必须弄清楚这些新语义如何在全局范围内工作。他们的解决方案是将全局环境分为两部分 - 一个用于旧样式,另一个用于新样式 - 但仍然“逻辑上”将其视为单一环境。来自 the specification:
A global Environment Record is logically a single record but it is specified as a composite encapsulating an object Environment Record and a declarative Environment Record.
“环境记录”是一个概念对象,它包含绑定²(变量等)和其他一些东西。将其与您在屏幕截图中看到的内容结合起来:
- “对象环境记录”是将全局对象的属性用于 var 范围绑定的记录。这就是 V8 在
[[Scopes]]
. 下调用的 Global
- “声明性环境记录”是保存词法范围绑定的记录(直接,不在单独的对象中)。这就是 V8 在
[[Scopes]]
. 下调用的 Script
在您的屏幕截图中,您有 let f
,它创建了一个名为 "f"
的词法范围绑定,因此 V8 在 [[Scopes]].Script
下显示了它。如果你有 var f
,V8 会在 [[Scopes]].Global
下显示。但同样,两者都是全局变量。
当他们说全球环境的两个部分“在逻辑上”是一个记录时,这是什么意思?基本上他们的意思是它 而不是 只是两个嵌套环境(尽管在很多方面它的行为就像它一样),只有 一个 全局范围(甚至尽管与之相关的环境有两个部分)。您可以看到的一种方法是您不能在全局范围内同时使用 var
和 let
声明某些内容,这是一个错误:
var a = 1;
let a = 2; // SyntaxError: Identifier 'a' has already been declared
如果它们只是 嵌套环境,您可以这样做 - 但那会是多么令人困惑!
但是虽然它们 不只是 嵌套,但它们 是 嵌套的。您可以通过在不使用 var
的情况下创建 var-scoped 全局来证明这一点(通过分配给全局对象上的 属性):
window.a = "var-scoped a";
let a = "lexically-scoped a";
console.log(a); // "lexically-scoped a"
console.log(window.a); // "var-scoped a"
let b = "lexically-scoped b";
window.b = "var-scoped b";
console.log(b); // "lexically-scoped b"
console.log(window.b); // "var-scoped b"
不言而喻,您不应该故意这样做,但它展示了双重环境的嵌套方面。
¹ 它确实有模块范围,这是不同的,但是像你这样的非模块脚本中的顶级代码是在全局范围内执行的。
² binding 是名称(如 a
)和当前值的存储槽的组合。变量是绑定。常量、参数、函数声明创建的变量,以及各种内置的东西,如 this
.
在 DevTools 控制台中检查函数的作用域时,我注意到一个 "script" 作用域。经过一些研究,它似乎是为 let
和 const
变量创建的。
没有 const
或 let
变量的脚本中函数的范围:
具有 let
变量的脚本中函数的作用域:
然而在控制台中打印了以下内容 1
- 脚本作用域中的变量仍然可以从其他脚本访问:
<script>let v = 1</script>
<script>console.log(v)</script>
我听说过 ES6 模块中的顶级变量无法从模块外部访问。这是示波器的用途还是有其他用途?
当您在顶层(即不在函数内部)使用 var
声明一个变量时,它会自动成为一个全局变量(因此在浏览器中您可以将其作为 属性 window
)。它与使用 let
和 const
声明的变量不同——它们不会成为全局变量。您可以在另一个脚本标记中访问它们,但不能将它们作为 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>
JavaScript 没有“脚本作用域”。¹您所看到的正是 Google 的 V8 JavaScript 引擎所称的全局环境部分它包含在全局范围内使用 let
、const
和 class
时创建的词法范围全局变量的新样式。它们仍然是全局变量,但它们不同于由 var
创建的旧式全局变量和全局范围内的函数声明(V8 在 Global
下的 [[Scopes]]
中显示)。 V8 调试器在这两个不同的地方列出了两种类型的全局变量。
如果你愿意,你可以在这里停止阅读,但如果你想要了解细节,请继续阅读。 :-)
那么为什么全球环境有两个全球部分?一句话:历史。
JavaScript 的原始形式的全局变量(global var-scoped bindings²)有多个问题。主要的两个是:
- 它们不仅仅是全局可用的标识符,它们也是全局对象的属性(
this
在全局范围内,也可以通过浏览器上的window
全局或较新的globalThis
规范定义的全局)。这意味着您可以在全局对象中查找您不知道其名称的内容(通过使用for-in
、Object.keys
或类似的方法)。 - 对同一标识符的重复声明不是错误。
除了全局范围内的那些问题之外,var
还存在没有块范围的问题;和块中的函数声明(也创建 var-scoped 绑定)未指定但允许作为扩展,导致它们在 JavaScript 实现中很大程度上不兼容的语义。
当需要添加一种声明具有更好语义的事物的新方法时(let
、const
、class
;“词法范围绑定”),委员会认为moves JavaScript forward (ECMA TC39) 必须弄清楚这些新语义如何在全局范围内工作。他们的解决方案是将全局环境分为两部分 - 一个用于旧样式,另一个用于新样式 - 但仍然“逻辑上”将其视为单一环境。来自 the specification:
A global Environment Record is logically a single record but it is specified as a composite encapsulating an object Environment Record and a declarative Environment Record.
“环境记录”是一个概念对象,它包含绑定²(变量等)和其他一些东西。将其与您在屏幕截图中看到的内容结合起来:
- “对象环境记录”是将全局对象的属性用于 var 范围绑定的记录。这就是 V8 在
[[Scopes]]
. 下调用的 - “声明性环境记录”是保存词法范围绑定的记录(直接,不在单独的对象中)。这就是 V8 在
[[Scopes]]
. 下调用的
Global
Script
在您的屏幕截图中,您有 let f
,它创建了一个名为 "f"
的词法范围绑定,因此 V8 在 [[Scopes]].Script
下显示了它。如果你有 var f
,V8 会在 [[Scopes]].Global
下显示。但同样,两者都是全局变量。
当他们说全球环境的两个部分“在逻辑上”是一个记录时,这是什么意思?基本上他们的意思是它 而不是 只是两个嵌套环境(尽管在很多方面它的行为就像它一样),只有 一个 全局范围(甚至尽管与之相关的环境有两个部分)。您可以看到的一种方法是您不能在全局范围内同时使用 var
和 let
声明某些内容,这是一个错误:
var a = 1;
let a = 2; // SyntaxError: Identifier 'a' has already been declared
如果它们只是 嵌套环境,您可以这样做 - 但那会是多么令人困惑!
但是虽然它们 不只是 嵌套,但它们 是 嵌套的。您可以通过在不使用 var
的情况下创建 var-scoped 全局来证明这一点(通过分配给全局对象上的 属性):
window.a = "var-scoped a";
let a = "lexically-scoped a";
console.log(a); // "lexically-scoped a"
console.log(window.a); // "var-scoped a"
let b = "lexically-scoped b";
window.b = "var-scoped b";
console.log(b); // "lexically-scoped b"
console.log(window.b); // "var-scoped b"
不言而喻,您不应该故意这样做,但它展示了双重环境的嵌套方面。
¹ 它确实有模块范围,这是不同的,但是像你这样的非模块脚本中的顶级代码是在全局范围内执行的。
² binding 是名称(如 a
)和当前值的存储槽的组合。变量是绑定。常量、参数、函数声明创建的变量,以及各种内置的东西,如 this
.