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。
总结
- 作用域 是变量的可访问范围,由代码结构静态决定。
- 作用域链 是变量查找的路径,基于函数定义时的词法环境。
- 闭包 通过保留作用域链,实现跨作用域访问变量。
