作用域链是JavaScript中变量查找的单向链表路径,由函数定义时的词法环境决定,逐级向上查找至全局环境;闭包使外层词法环境持久化,let/const存在TDZ,eval和with会动态修改链且应避免使用。

作用域链本质是嵌套作用域的查找路径
JavaScript 中变量不是“全局搜索”,而是沿着当前执行上下文的作用域链一级级向上找。这条链由函数定义时的词法环境决定,和函数在哪调用无关。每次进入函数,引擎会把该函数的 [[Environment]](内部属性)连到外层作用域的 LexicalEnvironment 上,形成一条单向链表。
变量查找过程:从当前作用域开始逐级向上
查找变量时,JS 引擎按以下顺序进行:
- 先查当前执行上下文的
LexicalEnvironment(含let/const声明和函数参数) - 没找到就查其
outer引用(即外层函数的词法环境) - 继续向上,直到全局环境(
globalThis或window) - 全程找不到就抛出
ReferenceError
注意:var 声明会被提升并绑定到函数作用域(或全局),但查找逻辑仍走同一链条;let/const 有暂时性死区(TDZ),未初始化前访问会报 ReferenceError,不是“找不到”而是“不可访问”。
闭包让外层作用域持久化,改变作用域链生命周期
当一个函数返回了内部函数,且该内部函数引用了外层变量,外层词法环境不会被销毁——它被保留在返回函数的 [[Environment]] 中。这意味着作用域链在函数调用结束后依然存在。
立即学习“Java免费学习笔记(深入)”;
function outer() {
const x = 42;
return function inner() {
console.log(x); // x 在 outer 的 LexicalEnvironment 中
};
}
const fn = outer();
fn(); // 仍能访问 x —— outer 的作用域链被 inner 持有
常见误判点:
-
for (var i = 0; i console.log(i), 0); }输出三个3:因为所有回调共享同一个var i,它属于outer函数作用域,循环结束时值为3 - 换成
let i就输出0,1,2:每个迭代创建独立绑定,每个回调闭包指向各自块级环境
eval 和 with 会动态修改作用域链,应避免使用
eval 在非严格模式下可向当前作用域注入变量,导致作用域链临时扩展;with 会把传入对象作为新的最内层作用域插入链首。两者都破坏静态可分析性,影响 JIT 编译优化,且极易引发意外覆盖。
例如:
function f() {
const a = 1;
with ({ a: 2 }) {
console.log(a); // 2 —— 不是从词法上确定的 a,而是运行时查 with 对象
}
}
V8、SpiderMonkey 等现代引擎对含 with 或非严格 eval 的函数直接禁用优化,性能断崖式下降。ES5 起严格模式已禁止 with,也限制 eval 的作用域污染能力。
真正难理解的不是“怎么找”,而是“找谁”——取决于函数定义的位置,而不是调用的位置;而最容易被忽略的,是 let 绑定的块级环境如何被闭包捕获、以及 TDZ 如何让“存在但不可读”成为常态。











