JavaScript 作用域与作用域链深度解析
一、作用域(Scope)
作用域 定义了变量、函数和对象的可访问范围。JavaScript 采用 词法作用域(静态作用域),即作用域由代码的书写位置决定,而非运行时调用位置。
1. 作用域类型
类型 | 定义 | 关键特性 |
---|---|---|
全局作用域 | 最外层环境 | 生命周期与页面一致,var 声明变量可被全局访问 |
函数作用域 | 函数内部 | var 声明的变量仅在函数内部有效 |
块级作用域 | {} 包裹的区域 | let/const 特有,ES6+ 特性,如 if 、for 中的变量隔离 |
2. 作用域示例
// 全局作用域
var globalVar = "全局变量";
function outer() {
// 函数作用域
var outerVar = "外层变量";
let blockVar = "块级变量(但属于函数作用域)";
if (true) {
// 块级作用域
let innerBlockVar = "内部块变量";
console.log(outerVar); // 可访问外层变量
}
console.log(innerBlockVar); // ReferenceError: innerBlockVar未定义
}
outer();
console.log(blockVar); // ReferenceError: blockVar未定义
二、作用域链(Scope Chain)
作用域链 是当前执行上下文中变量查找的链式结构,由当前作用域及其所有父级作用域组成。每个函数在 定义时 会记录其作用域链,形成闭包的基础。
1. 作用域链的形成
- 函数定义时:确定作用域链,基于代码的嵌套结构。
- 函数调用时:创建执行上下文,将作用域链复制到上下文中,并在前端添加当前活动对象(变量环境)。
function outer() {
var a = 10;
function inner() {
console.log(a); // 通过作用域链访问outer的变量
}
return inner;
}
const innerFunc = outer();
innerFunc(); // 输出10
2. 作用域链示意图
全局作用域 (global)
↑
outer函数作用域 (a: 10)
↑
inner函数作用域 (空)
inner
查找变量a
时,沿作用域链向上查找,直到在outer
的作用域中找到。
三、闭包与作用域链
闭包 是函数与其定义时的词法环境的组合。即使外部函数已执行完毕,内部函数仍可通过作用域链访问外部变量。
闭包示例
function createCounter() {
let count = 0; // 被闭包保留的变量
return {
increment: () => count++,
getCount: () => count
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1
count
变量被闭包保留,不会被垃圾回收,直到闭包不再被引用。
四、变量查找规则
- 从当前作用域开始查找:优先查找当前作用域的变量。
- 逐级向上回溯:若未找到,沿作用域链向上层作用域查找。
- 全局作用域终止:若全局作用域仍未找到,抛出
ReferenceError
。
var x = "global";
function test() {
var x = "local";
console.log(x); // "local"(当前作用域优先)
}
test();
五、关键问题解析
1. var
vs let/const
的作用域差异
var
:函数作用域,存在变量提升。console.log(a); // undefined(变量提升) var a = 10;
let/const
:块级作用域,存在暂时性死区(TDZ)。console.log(b); // ReferenceError(TDZ) let b = 20;
2. 循环中的闭包问题
- 错误示例(
var
导致共享变量):for (var i = 0; i < 5; i++) { setTimeout(() => console.log(i), 100); // 输出5次5 }
- 正确解决(
let
创建块级作用域):for (let i = 0; i < 5; i++) { setTimeout(() => console.log(i), 100); // 输出0,1,2,3,4 }
六、最佳实践
- 优先使用
let/const
:避免变量提升和全局污染。 - 合理管理闭包:及时释放不再使用的闭包,防止内存泄漏。
七、调试技巧
使用 Chrome DevTools 观察作用域链:
function outer() {
const a = 1;
function inner() {
debugger; // 断点调试
console.log(a);
}
inner();
}
outer();
- 在调试器的 Scope 面板中,可查看作用域链结构:
Local → Closure (outer) → Global
。
总结
- 作用域 是变量的可访问范围,由代码结构静态决定。
- 作用域链 是变量查找的路径,基于函数定义时的词法环境。
- 闭包 通过保留作用域链,实现跨作用域访问变量。