JavaScript 中的作用域与闭包:从底层原理到面试实战

Mia
Mia
发布时间:2026-10-15| 5人浏览过
原创

JavaScript 的面试常考作用域与闭包,其本质涉及引擎的编译执行机制、LHS/RHS 变量查询及词法环境绑定。本文从编译步骤、作用域嵌套规则、提升现象讲起,解析经典循环输出陷阱,并给出基于 let、IIFE 等标准解决方案。

结果与目标

许多开发者在面对如下代码时容易出错:

var arr = []
for (var i = 0; i < 3; i++) {
    arr[i] = function() {
        console.log(i);
    }
}
arr[0]() // 输出 3
arr[1]() // 输出 3
arr[2]() // 输出 3

目标是理解为何输出为 3 而非预期的 0, 1, 2,掌握闭包形成的条件,并能通过不同手段(如 ES6 let 或 IIFE)实现预期行为。

准备条件:JavaScript 的执行模型

JavaScript 并非单纯的解释型语言,而是即时编译(JIT)语言。在代码执行前,引擎会经历以下阶段:

  • 分词/词法分析:将字符分解为 token。
  • 解析/语法分析:生成抽象语法树(AST)。
  • 代码生成:编译器根据 AST 生成可执行代码,在此阶段完成变量声明的提升处理。

这种“先编译后执行”的特性导致了 console.log(a); var a = 1; 不会报错而是输出 undefined,因为声明被提升到了顶部。

真实操作顺序:作用域与变量查询

第 1 步:理解 LHS 与 RHS 查询

引擎在处理变量时会进行两种查询:

  1. RHS(Read)查询:查找变量的值。若在所有作用域链中找不到,抛出 ReferenceError。
  2. LHS(Write)查询:寻找变量的容器以赋值。若找不到且非严格模式,全局会自动创建该变量;严格模式下抛 ReferenceError。

此外,若 RHS 找到了变量但尝试对其进行非法操作(如调用数字),则抛出 TypeError。

第 2 步:确定词法作用域的查找规则

JavaScript 采用词法作用域,即作用域由代码书写位置决定,而非运行时。

  • 嵌套查找:当前作用域找不到时,向外层逐级查找,直至全局作用域。
  • 遮蔽效应:内部同名变量会屏蔽外部变量。
  • 欺骗词法:eval() 和 with 可在运行时修改作用域,但因性能和安全问题强烈不推荐使用。

第 3 步:掌握函数作用域与块作用域

ES3 之前主要依靠函数作用域。现代 JavaScript 通过以下关键字引入块作用域:

  • let:将变量绑定到块级作用域(如 for 循环头部),每次迭代都会重新声明,避免共享同一个引用。
  • const:类似 let,但值不可修改。

完成判断:闭包的识别与形成

MDN 定义:一个函数和对其周围状态(词法环境)的引用捆绑在一起,即为闭包。

核心判据是:函数是否在定义时的词法作用域之外被执行?

非闭包示例:函数内部调用,仍在原作用域内。

闭包示例:

function foo() {
    var a = 2;
    function bar() {
        console.log( a );
    }
    return bar; // 返回内部函数
}
var baz = foo(); // foo 执行完毕
baz();            // bar 仍能访问 a,形成闭包

闭包使得外层函数的局部变量在函数执行完后继续存活。

原文有依据的排错:经典面试题解析

回到开头的 for 循环问题。使用 var 时,i 是全局变量,所有匿名函数共享同一个 i 引用。循环结束时 i=3,故所有输出均为 3。

修正方案 1:使用 let 创建块级作用域

var arr = []
for (let i = 0; i < 3; i++) {
    arr[i] = function() {
        console.log(i);
    }
}
// 每次迭代 i 独立,输出 0, 1, 2

修正方案 2:使用立即执行函数 (IIFE)

var arr = []
for (var i = 0; i < 3; i++) {
    (function(j) {
        arr[j] = function() {
            console.log(j);
        }
    })(i)
}
// IIFE 捕获当前 i 的值作为参数,形成新的闭包

错误示范:var j = i

若在循环内写 var j = i,由于 var 无块级作用域,j 仍会被提升到外部作用域,最终三个函数指向同一个 j,导致输出全部为 2(最后一个循环结束前的值)。

简要总结

理解 JavaScript 的关键在于区分编译时(作用域/提升)和运行时(闭包/查询)。面试中关于作用域的考察,本质是对变量生命周期和内存引用的测试。以上就是 JavaScript 中的作用域与闭包的详细内容,更多关于 JavaScript 基础教程的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。