JS全局变量是如何工作的?

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

翻译: 刘小夕

在这篇博文中,我们将研究 JavaScript 的全局变量是如何工作的。如: scripts的范围,所谓的全局对象等等。

1.作用域

变量的词法作用域(简称:作用域)是可以访问它的程序的区域。 JavaScript 的作用域是静态的(它们在运行时不会改变)并且它们可以嵌套 - 例如:

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

if 语句引入的作用域(行B)嵌套在函数 func()(行A)的作用域内。

在示例中, func 是 if 的外层作用域。

2.词法作用域

在 JavaScript 语言规范中,作用域是通过词法作用域“实现”的。它们由两部分组成:

  • 将变量名映射到变量值的环境记录(可以想象成是字典)。这是 JavaScript 存储变量的地方。环境记录中的一个 key-value 条目称为绑定。

  • 对外部环境的引用 - 表示当前环境所代表的作用域的外部作用域的环境。

因此,嵌套作用域树可以由嵌套环境树表示。

3.全局对象

全局对象是一个对象,其属性是全局变量。

  • 无处不在: globalThis

  • 全局对象的其他名称取决于平台和语言构造:

    • window:是引用全局对象的经典方式,但它只适用于普通浏览器环境; 不在 Node.js 和 WebWorkers 中。

    • self:在浏览器中随处可用,包括 WebWorkers。但是 Node.js 不支持它。

    • global:仅在 Node.js 中可用。

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

4.全局环境

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

全局环境结合了两个环境记录

  • 对象式环境记录,其作用类似于普通环境记录,但保持其绑定与对象同步。在这种情况下,对象是全局对象。

  • 声明式环境记录。

下图显示了这些数据结构。

640?wx_fmt=jpeg

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

4.1创建变量

为了创建一个真正全局的变量,你必须处于全局作用域内 - 必须要在 scripts 的顶层:

  • 顶级 const, let 和 class 在声明式环境记录中创建绑定。

  • 顶级 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>

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

4.2读取/设置变量

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

<script>	
    let foo = 1; // 声明式环境记录	
    globalThis.foo = 2; // 对象式环境记录	
    console.log(foo); // 1 (声明式记录获胜)	
    console.log(globalThis.foo); // 2	
</script>

5.模块环境

每个模块都有自己的环境,它存储所有顶级声明 - 包括导入。模块环境的外部环境是全局环境。

结论:为什么JavaScript既有全局变量又有全局对象?

通常认为将全局变量挂载到全局对象上是错误的。因此,较新的构造(如 const, let 和 classes)会创建正常的全局变量,不会成为全局对象的属性。(在 script作用域内时)。

值得庆幸的是,大多数用现代 JavaScript 编写的代码都存在于 ECMAScript 模块和 CommonJS模块中,每个模块都有自己的作用域。

### 局部变量与全局变量的区别及其实现机制 #### 定义与作用域 局部变量是在函数或代码块内部声明的变量,其作用域仅限于该函数或代码块内部。这意味着局部变量只能在其定义的范围内访问,一旦离开该范围,变量将不再可用。局部变量通常用于存储临时数据,避免与其他函数或代码块中的变量发生冲突。例如,在 JavaScript 中: ```javascript function exampleFunction() { let localVar = "I'm local"; console.log(localVar); // 输出: I'm local } exampleFunction(); console.log(localVar); // 报错: localVar is not defined ``` 全局变量是在函数外部声明的变量,其作用域覆盖整个程序。全局变量可以在程序的任何地方访问和修改,包括函数内部。全局变量通常用于存储需要在多个函数或模块之间共享的数据。例如,在 JavaScript 中: ```javascript let globalVar = "I'm global"; function exampleFunction() { console.log(globalVar); // 输出: I'm global } exampleFunction(); console.log(globalVar); // 输出: I'm global ``` #### 实现机制 局部变量的生命周期通常与函数的调用周期一致。每当函数被调用时,局部变量会被创建,并在函数执行完毕后被销毁。局部变量存储在栈内存中,每次函数调用都会为其分配新的内存空间,确保每次调用之间的局部变量互不影响。这种机制有助于减少内存泄漏的风险,同时也提高了程序的安全性和可维护性。 全局变量的生命周期与程序的运行周期一致。它们在程序启动时被创建,并在程序结束时被销毁。全局变量存储在全局内存中,可以在程序的任何地方访问和修改。虽然全局变量提供了方便的数据共享方式,但过度使用可能导致命名冲突、难以调试的问题,以及增加程序的复杂度。因此,在实际开发中应谨慎使用全局变量,尽量通过模块化设计或使用闭包等方式来替代全局变量的使用。 在某些编程环境中,如 Adobe After Effects (AE),表达式中直接定义的变量是局部变量,它们仅在其所属的图层或属性的表达式上下文中有效。然而,AE 也提供了一系列全局对象、函数以及通过表达式控制层实现的数据共享机制,这些可以被视为表达式环境中的全局资源。[^3] #### 性能与最佳实践 在性能方面,局部变量通常比全局变量更快访问,因为它们存储在栈内存中,而全局变量可能需要通过作用域链来查找。作用域链是一种机制,用于确定变量的访问权限和可见性。当在一个函数内部访问一个变量时,JavaScript 引擎会首先在当前作用域中查找该变量,如果没有找到,则会沿着作用域链向上查找,直到找到该变量或到达全局作用域为止。这种机制确保了变量的正确性和安全性,但也可能导致性能上的开销。 为了避免全局变量污染,建议采取以下措施: - 尽量使用局部变量代替全局变量。 - 使用模块化设计,将相关的功能封装在一个模块中,减少全局变量的使用。 - 利用闭包特性,创建私有变量和方法,防止外部直接访问和修改。 #### 示例代码 以下是一个展示局部变量和全局变量行为差异的示例: ```javascript let globalCounter = 0; function incrementGlobal() { globalCounter++; console.log("Global counter:", globalCounter); } function incrementLocal() { let localCounter = 1; localCounter++; console.log("Local counter:", localCounter); } incrementGlobal(); // 输出: Global counter: 1 incrementLocal(); // 输出: Local counter: 2 incrementGlobal(); // 输出: Global counter: 2 incrementLocal(); // 输出: Local counter: 2 ``` 在这个示例中,`globalCounter` 是一个全局变量,每次调用 `incrementGlobal` 函数时都会递增。而 `localCounter` 是一个局部变量,每次调用 `incrementLocal` 函数时都会重新初始化为 1,然后递增到 2,因此每次调用的结果都是 2。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值