JavaScript 中的作用域链与闭包详解:从词法作用域看原理
本文深入解析 JavaScript 中的三大核心概念:作用域链、词法作用域与闭包。通过具体代码案例分析作用域的查找顺序,阐明词法作用域(静态作用域)的决定性因素,并揭示闭包在函数执行结束后仍保持对内部变量引用的底层机制。
结果或目标
理解 JavaScript 引擎在运行代码时,如何通过作用域链定位变量标识符,以及为何某些变量能在函数返回后依然保存在内存中。
准备条件
需要了解 JavaScript 的基本语法,熟悉函数的定义与调用,以及 `var`、`let`、`const` 等变量声明方式的区别。
真实操作顺序
第1步:理解基本的作用域链查找逻辑
在分析复杂情况前,先回顾常规的作用域表现。当代码访问变量时,JavaScript 引擎遵循“由内向外”的查找规则。
var a = '喜羊羊';
function A(){
console.log(a); // 1. 打印 '喜羊羊'
a = '美羊羊'; // 2. 修改全局变量 a
function B(){
console.log(a); // 3. 打印 '美羊羊'
}
B();
}
A();在这个例子中,B 函数位于 A 函数内部,A 函数位于全局作用域。当 B 需要查找变量 a 时:
- 首先在
B自身的局部作用域中查找,未找到。 - 向上查找至外层
A作用域,仍未找到。 - 继续向上查找到全局作用域,找到
var a。
这就是作用域链的基本体现:当前层找不到,就去找上一级。
第2步:剖析词法作用域对外部引用的影响
很多时候,开发者会误以为函数内部的变量查找取决于函数的“调用位置”,但这正是常见的误区。JavaScript 采用的是词法作用域(Lexical Scope),也称为静态作用域。
词法作用域意味着作用域是由代码编写时的物理位置决定的,而不是运行时动态决定的。每个执行上下文的变量环境中都包含一个 outer 引用(通常指向
所示的内部结构),用于指向定义该函数时的外部执行上下文。
来看一个容易掉坑的例子:
function bar() {
console.log(myName) // 这里的 myName 是多少?
}
function foo() {
var myName = "极客邦"
bar() // 注意这里是调用 bar,而非 bar 被定义在 foo 内
}
var myName = "极客时间"
foo()直觉上可能会认为打印出 “极客邦”,但实际结果是 “极客时间”。
原因解析:
bar 函数是在全局作用域下定义的,而不是在 foo 函数内部定义的。根据词法作用域规则,bar 创建时的 outer 引用指向的是全局环境。因此,无论 bar 在哪里被调用(即使是在 foo 内部),它查找变量的起点始终是其定义时的外部环境——即全局作用域。在全局作用域中,myName 的值为 “极客时间”。

从上图可以看出,整个词法作用域链的顺序是固定不变的:bar 函数作用域 → 全局作用域。它不包含 foo 函数的作用域,因为 bar 在代码结构中并没有嵌套在 foo 内部。
结论: 作用域链在代码编译阶段就已经确定,只与代码的物理位置有关,与函数调用时机无关。
第3步:掌握闭包的运行机制
闭包(Closure)是 JavaScript 中最强大也最常被讨论的概念。从上面的例子延伸,如果内部函数引用了外部函数的变量,那么当外部函数执行完毕后,这些变量会被回收吗?
观察以下代码:
function foo() {
var myName = "极客时间"
let test1 = 1
const test2 = 2
var innerBar = {
getName: function(){
console.log(test1)
return myName
},
setName: function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName("极客邦")
console.log(bar.getName()) // 输出:1, "极客邦"这里存在一个关键疑问:foo 函数执行完毕后,其执行上下文从调用栈中弹出,按照常规逻辑,局部变量应当被垃圾回收。为什么 bar.getName() 依然能访问到 myName 和 test1?

原理分析:
当 foo 返回对象 innerBar 时,由于 innerBar 中的 getName 和 setName 方法(闭包)引用了 foo 内部的变量 myName 和 test1,JavaScript 引擎检测到这些外部变量仍有活跃的外部引用,因此不会将它们从内存中清除。
可以将这理解为给 setName 和 getName 配备了一个专属背包,里面装着它们所依赖的外部变量快照。只要这两个方法还存在(如赋值给 bar),这个背包就会一直存在于内存中。
完成判断
闭包的定义: 能够访问其他函数内部变量的函数,被称为闭包。更严谨地说,在 JavaScript 中,内部函数总是可以访问其外部函数中声明的变量;当外部函数返回内部函数或其方法后,即使外部函数已结束,内部函数仍能引用这些变量,这些变量的集合即为闭包。
判断是否形成闭包的关键标准:
1. 函数嵌套。
2. 内部函数引用了外部函数的变量。
3. 外部函数返回了内部函数(或被其他方式持有引用)。
原文有依据的排错
常见错误在于混淆“调用位置”与“定义位置”。许多开发者认为函数在哪里被调用,它的词法环境就在哪里。请务必记住:词法作用域只看定义位置,不看调用位置。这也是调试跨模块作用域问题时最容易产生的误区。
简要总结
JavaScript 的作用域链由词法作用域静态决定,决定了变量查找的路径;而闭包则是这一机制带来的副作用与应用,允许内部函数在外部生命周期结束后依然访问私有变量。理解这两者,是写出健壮、可维护前端代码的基础。
以上就是 JavaScript 中的作用域链与闭包原理的详细解读,更多关于 JavaScript 进阶知识的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









