let语句会在全局对象上创建属性吗?

64
在JavaScript中,var声明会在全局对象上创建属性:
var x = 15;
console.log(window.x); // logs 15 in browser
console.log(global.x); // logs 15 in Node.js

ES6 引入了具有块级作用域的 let 声明,引入了词法作用域。

let x = 15;
{
   let x = 14;
}
console.log(x); // logs 15;

然而,这些声明是否会在全局对象上创建属性呢?

let x = 15;
// what is this supposed to log in the browser according to ES6?
console.log(window.x); // 15 in Firefox
console.log(global.x); // undefined in Node.js with flag

@jfriend00 是的,而且它在Firefox和io.js这两个我可以测试的环境中表现出不同的行为。 - Benjamin Gruenbaum
在node.js的repl中,如果你执行var x = 15,它将作用于全局范围,你可以在下一行输入global.x轻松验证。 - Benjamin Gruenbaum
1
问题:全局作用域是否有与之关联的“词法环境”和相关的“环境记录”? - Ben Aston
回答我的问题:是的和是的。第8.1段第4句。https://people.mozilla.org/~jorendorff/es6-draft.html#sec-lexical-environments - Ben Aston
我认为这是Firefox相关的错误报告:https://bugzilla.mozilla.org/show_bug.cgi?id=589199 - Felix Kling
在浏览器中:window.x或globalThis.x - oceanGermanique
5个回答

66

let语句是否会在全局对象上创建属性?根据规范(spec),不会:

引用规范的原话如下:

全局环境记录逻辑上是单个记录,但被指定为一个包含对象环境记录和一个声明式环境记录的组合体。其中对象环境记录以关联Realm的全局对象作为其基础对象。这个全局对象是全局环境记录的GetThisBinding具体方法返回的值。全局环境记录的对象环境记录组件包含所有内置全局变量的绑定(第18条),以及由全局代码中的FunctionDeclarationGeneratorDeclarationVariableStatement引入的所有绑定。全局代码中所有其他ECMAScript声明的绑定都包含在全局环境记录的声明式环境记录组件中。

进一步解释如下:

  • 一个声明式环境记录将绑定存储在内部数据结构中。无论如何,都无法以任何方式得到该数据结构(考虑函数作用域)。

  • 一个对象环境记录使用实际的JS对象作为数据结构。对象的每个属性成为一个绑定,反之亦然。全局环境具有一个对象环境对象,其“绑定对象”是全局对象。另一个例子是with

现在,正如引用部分所述,只有FunctionDeclarationGeneratorDeclarationVariableStatement在全局环境的对象环境记录中创建绑定。也就是说,只有这些绑定成为全局对象的属性。

所有其他声明(例如constlet)存储在全局环境的声明式环境记录中,它不基于全局对象。


1
但是8.1.1.1(https://people.mozilla.org/~jorendorff/es6-draft.html#sec-declarative-environment-records)指出,`let`和`var`创建相同类型的环境记录(即它们都创建`声明性环境记录`),这表明`let`和`var`在全局对象上的位置可能对应。然而,我想全局情况可能是特殊的。 - Ben Aston
1
letvar不会创建记录。letvar绑定是存储在环境记录中的。而且,有不同类型的环境记录:声明性(你链接到的),对象,函数,模块和全局,其中声明性和对象记录是两种主要类型(其他所有类型都是派生类型)。 - Felix Kling
请问您能否澄清您区分的目的。如果我在全局范围内声明一个变量,那么会创建一个环境记录(并且像您所说的那样将变量存储在其中)。所以我们都是“正确”的,对吗?您是说使用的环境记录类型取决于我们是否处于全局范围内? - Ben Aston
Felix,你能否确认你引用的 VariableStatement 是指特定于函数作用域的声明,还是也包括 let?我猜想应该是前者。 - Ben Aston
9
澄清一些事情:声明式记录和对象记录的主要区别在于声明式记录使用内部数据结构存储绑定信息,而对象记录则使用实际的JS对象作为“存储”。在ES5中,全局环境被定义为对象记录 (http://es5.github.io/#x10.2.3),这就是为什么全局作用域中的所有变量声明都成为全局对象的属性的原因。然而,在ES6中,全局环境由两种不同的环境记录类型组成,只有 var 声明存储在对象记录中,而 let 声明不会。 - Felix Kling
显示剩余13条评论

7

标准脚本:

如果在脚本顶层声明,letvar 变量都可以在脚本文件外访问。但是,只有 var 变量会被赋值给 window 对象。看一下这段代码片段作为证明:

<script>
  var namedWithVar = "with var";
  let namedWithLet = "with let";
</script>

<script>
  console.log("Accessed directly:");
  console.log(namedWithVar);        // prints: with var
  console.log(namedWithLet);        // prints: with let
  console.log("");

  console.log("Accessed through window:");
  console.log(window.namedWithVar); // prints: with var
  console.log(window.namedWithLet); // prints: undefined
</script>

Javascipt模块:

请注意,模块是一个不同的概念。在模块中声明的变量不能在全局范围内使用:

<script type="module">
  var namedWithVar = "with var";
  let namedWithLet = "with let";
</script>

<script>
  console.log(namedWithVar); // ReferenceError
</script>

<script>
  console.log(namedWithLet); // ReferenceError
</script>

<script>
  console.log(window.namedWithVar); // prints: undefined
  console.log(window.namedWithLet); // prints: undefined
</script>


6

根据规范:

“let和const声明定义了作用域限定于运行执行上下文的词法环境的变量。”

这意味着您应该能够在执行范围内访问变量,但不能在外部访问。这将执行范围扩展到经典JS闭包结构之外,包括函数或全局。

在全局范围内定义let变量不应将变量暴露在全局上下文中,就像在Firefox中曾经一样。实际上,您不应该在全局范围内定义变量。


3
将一个let变量定义为全局变量会存在歧义,但从我的回答可以看出并非如此。你所说的是正确的,我们确实有更多的东西可以创建作用域(例如模块),然而,这并不能完全回答提出的问题。 - Felix Kling
1
Firefox似乎已经改变了其行为,使其与V8保持一致,不再将let和const声明绑定到全局范围。 - ecc521

1

let关键字声明的变量不会在全局对象(window用于浏览器)上创建可访问的属性。 实际上,Firefox已经修复了这个问题:let v = 42; 'v' in window // false


0

let 允许您声明变量,其作用域仅限于使用它的块、语句或表达式。这与 var 关键字不同,后者定义全局变量或整个函数的局部变量,而不考虑块作用域。

在程序和函数的顶层,与 var 不同,let 不会在全局对象上创建属性。例如:

   var x = 'global';
   let y = 'global';
   console.log(this.x); // "global"
   console.log(this.y); // undefined

var声明的变量作用域是其当前执行上下文,即封闭函数或对于在任何函数外部声明的变量来说是全局的。如果重新声明JavaScript变量,则不会丢失其值。例如:

var x = 1;

if (x === 1) {
  var x = 2;

  console.log(x);
  // output: 2
}

console.log(x);
// output: 2

注意:CC++Java不同,使用var声明变量时,JavaScript没有块级作用域。

正如我们之前提到的,let允许您声明仅限于块、语句或表达式的范围内的变量。例如:

let x = 1;

if (x === 1) {
  let x = 2;

  console.log(x);
  // output: 2
}

console.log(x);
// output: 1

在这里,我建议你阅读有关变量作用域的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接