JavaScript 的全局变量如何真正地工作

JavaScript 的全局变量如何真正地工作

在这篇博客文章中,我们调查 JavaScript 的全局变量如何工作。几个有趣的想象在发生:脚本作用域、被称作全局对象以及其它更多。

作用域

变量的词法作用域(简称:作用域)是可以访问它的区域。JavaScript 的作用域是静态的(它们在运行时不会变化)并且可以嵌套,举例来说:

function func() {  // (A)
  const foo = 1;  if (true) {
    // (B)
    const bar = 2;
  }
}

if 声明(line B)的作用域引用嵌套在函数 func()(line A) 的内部作用域。

作用域 S 最深的作用域是作用域 S 的外部作用域。在你之中,funcif 的外部作用域。

词法环境

在 JavaScript 的语言规范中,作用域是通过词法环境(lexical environments)来执行的。它们由两部分组成:

  • 将变量名称映射到变量值的环境记录(思考字典)。这是 JavaScript 存储变量的地方。环境记录中的一个键值条目称为绑定
  • 外部环境的引用 - 表示当前环境所代表的作用域的外部作用域的环境。

于是嵌套作用域树由嵌套的环境树声明,外部引用链接在一起。

全局对象

全局对象是指属性是全局变量的对象。我们很快就会检查它是如何适应环境树的,它有几个不同的名称:

  • 存在任何位置(功能提案):globalThis
  • 全局对象的其它名称取决于平台和语言构造:
    • window: 是全局对象的经典引用。但是它仅仅运行在一般浏览器代码中;不能在 Node.js 和 Web Workers 中使用。
    • self: 在浏览器中随处可用,包括在 Web Workers。但是 Node.js 没有提供支持。
    • global: 仅仅在 Node.js 中可用。

全局对象包含所有的内置全局变量。

全局环境

全局作用域是最外层的作用域 - 它没有上层作用域。它的环境是全局环境(global environment)。每个环境都通过由外部引用链接的一系列环境与全局环境相关联。全局环境的外部引用是 null

全局环境联合了两个环境记录:

  • 一个对象环境记录工作像一个通用的环境记录,但与对象保持其绑定同步。在这种情况下,对象是全局对象。
  • 普通(声明性)环境记录。

下图显示了这些数据结构。脚本作用域和模块环境很简单地解释。

JavaScript environments

接下来的两个小节将解释如何组合对象记录和声明性记录。

创建变量

为了创建一个真正全局的变量,必须处于全局作用域内 - 这只是顶层脚本的情况:

  • 顶级 constletclass 建立在声明记录绑定。
  • 顶级 var 和函数声明在对象记录中创建绑定。
<script>
  const one = 1;
  var two = 2;
</script>
<script>
  // All scripts share the same top-level scope:
  console.log(one); // 1
  console.log(two); // 2

  // Not all declarations create properties of the global object:
  console.log(window.one); // undefined
  console.log(window.two); // 2
</script>

另外,全局对象包含所有内置全局变量,并通过对象记录将它们贡献给全局环境。

获取或设置变量

当我们获取或设置变量并且两者环境记录都具有该变量的绑定时,声明性记录将获胜:

<script>
  let foo = 1; // 声明环境记录
  globalThis.foo = 2; // 对象环境记录

  console.log(foo); // 1 (声明记录获胜)
  console.log(globalThis.foo); // 2
</script>

模块环境

每个模块都有它自己的环境上下文。它存储所有的顶级声明 - 包含导入的(imports)。模块环境的外部环境是全局环境。

总结:为什么 JavaScript 拥有通用全局变量和全局对象两者?

通常认为全局对象是一种误解。因此,较新的构造(如 constlet 和类)会创建正常的全局变量(在脚本作用域内)。

值得庆幸的是,大多数用现代 JavaScript 编写的代码都存在于 ECMAScript 模块和 CommonJS 模块中。每个模块都有自己的作用域,这就是为什么管理全局变量的规则很少对基于模块的代码很重要。

更多阅读

本文作者 Axel Rauschmayer,转载请注明来源链接:

原文链接:https://2ality.com/2019/07/global-scope.html

本文链接:https://tie.pub/2019/07/global-scope/