源本科技 | 码上会

JavaScript 变量作用域

2025/12/30
18
0

在 JavaScript 中,作用域 决定了变量在程序中的可访问范围。理解作用域机制,是避免变量污染、命名冲突和“意外行为”的关键,也是写出清晰、安全代码的基础。


学习目标

  • 掌握全局作用域与局部作用域的区别

  • 理解 varletconst 在作用域上的差异

  • 深入理解块级作用域与词法作用域(闭包基础)

  • 了解 ES6 模块作用域的特性

  • 避免因作用域不清导致的常见 bug


全局与局部作用域

全局作用域

任何函数或代码块外部声明的变量,属于全局作用域,可在程序任何地方访问

// 全局变量
const companyName = "TechCorp";

function greet() {
    console.log(companyName); // 可访问全局变量
}

greet(); // 输出:TechCorp

风险:过多全局变量会导致:

  • 命名冲突

  • 意外修改

  • 难以调试(“谁改了我的变量?”)


局部作用域

函数内部声明的变量,仅在该函数内有效,外部无法访问。

function sayHello() {
    let user = "Alice"; // 局部变量
    console.log(user);
}

sayHello();      // 输出:Alice
// console.log(user); // 报错:user is not defined

优点:封装性强,避免污染全局环境。

注意:在 ES6 之前,JavaScript 只有函数作用域(通过 var 实现),没有真正的块级作用域。


varletconst

声明方式

作用域类型

是否可重复声明

是否可重新赋值

是否存在变量提升

var

函数作用域(或全局)

✅ 是

✅ 是

✅ 是(提升至函数顶部)

let

块级作用域

❌ 否

✅ 是

⚠️ 存在“暂时性死区”

const

块级作用域

❌ 否

❌ 否(引用不可变)

⚠️ 存在“暂时性死区”


示例:var 无块级作用域

{
    var x = 10;     // var 不受 {} 限制
    let y = 20;
    const z = 30;
    
    console.log(x, y, z); // 10 20 30
}

console.log(x); // 10(var 可在块外访问)
// console.log(y); // ReferenceError
// console.log(z); // ReferenceError

关键结论

  • letconst 具有块级作用域(由 {} 定义)

  • var 只有函数作用域全局作用域


块级作用域

{} 包裹的代码区域(如 ifforwhile、独立代码块等)构成一个块作用域

if (true) {
    let message = "Hello";
    const version = "1.0";
    var legacy = "old"; // 不受块限制
}

// console.log(message); // 错误
// console.log(version); // 错误
console.log(legacy);     // "old"

优势

  • 更精细的变量控制

  • 避免循环变量泄漏(见下例)

经典问题:var 在循环中的陷阱

// 使用 var(有问题)
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// 输出:3 3 3(因为 i 是全局的,循环结束后 i=3)

// 使用 let(正确)
for (let j = 0; j < 3; j++) {
    setTimeout(() => console.log(j), 100);
}
// 输出:0 1 2(每次迭代创建新的 j)

最佳实践优先使用 letconst,避免 var


词法作用域

也称为静态作用域,指函数的作用域在定义时就已确定,而非调用时。

function outer() {
    const x = 10;

    function inner() {
        const y = 20;
        console.log(x, y); // 可访问 outer 的 x
    }

    inner();
}

outer(); // 输出:10 20

原理

  • inner 函数在其定义位置“记住”了 outer 的作用域

  • 这种机制是 闭包 的基础

词法作用域 = 函数能访问其外层函数中定义的变量。


模块作用域

ES6 引入了模块系统,每个 .js 文件默认是一个独立模块,拥有自己的作用域。

// math.js
const PI = 3.14159; // 私有变量,外部不可见

export function area(r) {
    return PI * r * r;
}

// main.js
import { area } from './math.js';
console.log(area(2)); // 12.566...
// console.log(PI); // 无法访问

特点

  • 模块内所有声明默认私有

  • 必须通过 export 显式导出

  • 通过 import 导入其他模块内容

  • 彻底解决全局污染问题

注意:模块需在 <script type="module"> 或构建工具中使用。


作用域类型对比

作用域类型

范围

关键字

特点

全局作用域

整个程序

var / let / const(在顶层)

所有地方可访问

函数作用域

单个函数内

var

ES5 主要作用域

块级作用域

{} 内部

let / const

ES6 核心特性,更安全

词法作用域

嵌套函数链

所有

支持闭包

模块作用域

单个文件

所有(默认私有)

现代项目组织标准


最佳实践

  1. 永远不要使用 var,改用 let(可变)或 const(不可变)

  2. 优先使用 const,除非明确需要重新赋值

  3. 避免创建不必要的全局变量

  4. 利用块级作用域限制变量生命周期

  5. 使用 ES6 模块组织代码,实现天然作用域隔离


思考题

  1. 以下代码输出什么?为什么?

    console.log(a); // ?
    console.log(b); // ?
    var a = 1;
    let b = 2;
  2. 如何修复下面的代码,使其输出 0, 1, 2

    for (var i = 0; i < 3; i++) {
        setTimeout(() => console.log(i), 100);
    }
  3. 为什么说“词法作用域”是闭包的基础?请用代码说明。