深度剖析JavaScript作用域:从局部到全局一网打尽

作者:函数茶馆时间:2026-10-26 14:38:57浏览:101

本文深入解析JavaScript作用域机制,从编译原理的词法分析与语法分析入手,阐述引擎、编译器与作用域的协作。详细区分LHS与RHS查询行为,解析非严格与严格模式下变量查找、隐式全局变量创建及异常抛出的具体差异,帮助开发者全面理解从局部到全局的作用域规则。

深入解析JavaScript作用域机制:从局部变量到全局变量的完整指南

1.1 编译原理基础

  • JavaScript 实际上是一门编译语言。

在传统编译语言中,源代码在执行前通常会经历三个主要步骤:

分词与词法分析(Tokenizing/Lexing)

var a = 2;
// 分解后:
var、a、=、2、;
// 空格是否会被当做词法单元,取决于空格在这门语言中是否具有意义。
  • 该阶段包含两个子过程:分词(tokenizing)和词法分析(Lexing)。两者的核心区别在于词法单元的识别是通过有状态还是无状态的方式进行的。

解析与语法分析(Parsing)

  • 此过程将词法单元流(数组)转换为一个由元素逐级嵌套组成的、代表程序语法结构的树,该树被称为“抽象语法树”(Abstract Syntax Tree, AST)。

代码生成

  • 将 AST 转换为可执行代码的过程被称为代码生成。这意味着存在某种机制,将 `var a = 2;` 的 AST 转换为一组机器指令,用于创建一个名为 a 的变量(包含内存分配等),并将值存储于 a 中。
  • 与其他仅包含这三个步骤的编译语言不同,JavaScript 引擎更为复杂。在语法分析和代码生成阶段,它拥有特定的步骤来对比运行性能并进行优化,包括对冗余元素的优化。
  • 简而言之,任何 JavaScript 代码片段在执行前都必须经过编译(通常就在执行前)。

1.2 理解作用域

1.2.1 角色分配

  • 引擎:从头到尾负责整个 JavaScript 程序的编译及执行过程。
  • 编译器:引擎的合作伙伴之一,负责语法分析及代码生成等底层工作。
  • 作用域:引擎的另一个合作伙伴,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实行一套严格的规则,确定当前执行的代码对这些标识符的访问权限。

1.2.2 交互对话

  • var a = 2; 这段代码是一句声明。但会经过编译器和引擎的处理来进行。
  • S: 变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它进行赋值。

1.2.3 编译器的视角

编译器在编译过程中的第二步中生成了代码,引擎执行它时,会通过查找变量 a 来判断他是否已声明过。查找的过程由作用域进行协助,但是引擎执行怎样的查找会影响最终的查找结果。

引擎常使用的查询类型为:LHS和RHS

LHS: 赋值操作的目标是谁

RHS: 谁是赋值操作的源头

1.2.5

function foo(a) {
    var b = a;
    return a + b;
}
var c = foo(2);
// 对话:
1. 声明 var c
2. 对 c 进行 LHS
3. 对 foo(2) 进行 RHS
4. function foo(a) 期间会进行 a = 2, 对 a 进行 LHS
5. 声明 var b
6. 对 b 进行 LHS
7. 对 a 进行 RHS
8. return a + b; 分别对 a、b 进行 RHS
// 答案:
1. 所有的 LHS(一共有3处)
    1. c =..;
    2. a = 2(隐士变量分配)
    3. b = ..
2. 所有的 RHS (一共有4处)
    1. foo(2..
    2. = a;
    3. a..
    4. .. b

1.3 作用域嵌套

作用域是根据名称查找变量的一套规则。

当一个块或函数嵌套在另一个块或函数中时,就会发生作用域的嵌套。因此在当前作用域中无法找到某个变量时,引擎就会在外层作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。

// 非严格模式下
function foo(a) {
console.log(a + b);
}
var a = 2;
foo(2); // 4
// 严格模式下:
function foo(a) {
console.log(a + b);
}
var a = 2;
foo(2); // 4

遍历嵌套作用域链的规则:引擎会从当前的执行作用域中开始查找变量,如果找不到就会向上一级中继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找的过程都会停止。

例子:

深入解析JavaScript作用域机制:从局部变量到全局变量的完整指南

  • 整个建筑代表程序中的嵌套作用域链,第一层楼代表当前的执行作用域,也就是你所处的位置。建筑的顶层代表全局作用域。
  • 引擎查找的方式:LHS 和 RHS 引用会先在当前楼层中进行查找,如果没找到,就会坐电梯前往上一层楼楼,如果还是没找到就会继续上下,以此类推。一旦达到了顶层(全局作用域), 可能找到你了你所需的变量,也可能没找到,但无论如何查找过程都会停止。

1.4 异常处理

为什么区分 LHS 与 RHS 是一种重要的事?

因为在变量还未声明(在任何作用域中都无法找到该变量)的情况下,引擎的这两种查询行为是不一样的。

// 非严格模式下:
function foo(a) {
console.log(a + b);
b = a;
}
foo(2); // 4
// 严格模式下:
'use strict';
function foo(a) {
console.log(a + b);
b = a;
}
foo(2); // ReferenceError: b is not defined

上述代码引擎行为:

  • 非严格模式下:

第一次对 b(.. + b) 进行 RHS 查询时未找到该变量,也就是说,这是一个“未声明” 的变量,因为在任何相关的作用域都无法找到它。

第二次对 b(b = ..) 进行 LHS 查询时,如果在顶层(全局作用域)中也没找到该变量,就会在全局作用域中隐式地创建一个该名称的变量,并将其返回给引擎。

......

  • 严格模式下:

第一次对 b(.. + b) 进行 RHS 查询时未找到该变量,也就是说,这是一个“未声明” 的变量,因为在任何相关的作用域都无法找到它,直接抛出 'ReferenceError'。

......

  • 非严格模式下引擎查找规则:

    当引擎执行 RHS 查询在所有嵌套的作用域中找不到所需的变量,引擎就会抛出 ReferenceError 异常。

    当引擎执行 LHS 查询时,如果在顶层作用域中也无法找到该变量,全局作用域就会创建一个该名称的变量,并将其返回给引擎(非严格模式下)。

  • 严格模式下引擎查找规则:

    ES5 引入了 "严格模式"(use strict),在行为上有很多不同,其中一个不同的行为就是严格模式下禁止自动或隐式地创建全局变量。因此在严格模式中引擎执行 LHS 查询失败时,并不会创建一个全局变量,而是直接抛出一个 ReferenceError。

    如果 RHS 找到了一个变量,但尝试对这个变量进行一些不合理的操作时,比如对一个非函数类型的值进行函数调用,或者引用 null 或 undefined 类型的之中属性,那引擎则会抛出另外一种类型的异常 TypeError。

  • ReferenceError 同作用域判断失败相关,而 TypeError 代表作用域判别成功了,但对结果的操作是非法或不合理的。

1.5 小结

作用域是根据名称查找变量的一套规则。

引擎常使用的查询类型为:LHS 和 RHS

  • LHS: 赋值操作的目标是谁
  • = 操作符在调用函数时的形参会导致关联作用的赋值操作。也就是说 foo (a, b, c...), 都会有 a = xxx, b = xxx, c = xxx ...... 的行为。
  • RHS: 谁是赋值操作的源头

非严格模式下引擎查找规则:

当引擎执行 RHS 查询在所有嵌套的作用域中找不到所需的变量,引擎就会抛出 ReferenceError 异常。

当引擎执行 LHS 查询时,如果在顶层作用域中也无法找到该变量,全局作用域就会创建一个该名称的变量,并将其返回给引擎(非严格模式下)。

严格模式下引擎查找规则:

ES5 引入了 "严格模式"(use strict),在行为上有很多不同,其中一个不同的行为就是严格模式下禁止自动或隐式地创建全局变量。因此在严格模式中引擎执行 LHS 查询失败时,并不会创建一个全局变量,而是直接抛出一个 ReferenceError。

如果 RHS 找到了一个变量,但尝试对这个变量进行一些不合理的操作时,比如对一个非函数类型的值进行函数调用,或者引用 null 或 undefined 类型的之中属性,那引擎则会抛出另外一种类型的异常 TypeError。

  • ReferenceError 同作用域判断失败相关,而 TypeError 代表作用域判别成功了,但对结果的操作是非法或不合理的。

特殊字符描述:

  • 问题标注 Q:(question)
  • 答案标注 R:(result)
  • 注意事项标准:A:(attention matters)
  • 详情描述标注:D:(detail info)
  • 总结标注:S:(summary)
  • 分析标注:Ana:(analysis)
  • 提示标注:T:(tips)

以上就是深度剖析JavaScript作用域从局部到全局一网打尽的详细内容,更多关于JavaScript作用域局部全局的资料请关注本站其它相关文章!

作者最新文章