#JavaScript 中的 const、TDZ、调用栈与调用堆详解

200 阅读3分钟

const 关键字

const 是 ES6 引入的声明常量的关键字,具有以下特性:

基本特性

  • 不可重新赋值:const 声明的变量不能被重新赋值
  • 必须初始化:声明时必须立即赋值
  • 块级作用域:只在声明所在的块级作用域内有效

简单数据类型 vs 复杂数据类型

const age = 18; // 简单数据类型
// age = 20; // 报错,不能重新赋值

const friends = [
  { name: "张三", hometown: '上饶' },
  { name: "李四", hometown: '赣州' }
]; // 复杂数据类型

// 可以修改对象内容
friends.push({ name: "王五", hometown: '九江' }); // 允许
// friends = []; // 报错,不能重新赋值

关键区别

  • 简单数据类型:值不可变(内存栈中的值不变)
  • 复杂数据类型:对象内容可变,但引用地址不可变

TDZ(Temporal Dead Zone,暂时性死区)

TDZ 是 ES6 引入的概念,与 let 和 const 相关:

console.log(a); // undefined (var 声明提升)
var a = 1;

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;

TDZ 规则

  1. let/const 声明会提升到块顶部,但不会初始化
  2. 从块开始到声明语句执行前,变量处于 TDZ
  3. 访问 TDZ 中的变量会抛出 ReferenceError
  4. 声明语句执行后,变量离开 TDZ

设计目的

  • 避免 var 的变量提升带来的不可预测行为
  • 使代码更易理解和调试
  • 强制开发者遵循"先声明后使用"的良好实践

调用栈(Call Stack)与调用堆(Heap)

内存模型

调用栈 (Stack)调用堆 (Heap)
存储内容原始值、引用地址对象、数组等复杂数据结构
分配方式连续内存空间动态分配的非连续内存
访问速度
大小限制较小(通常几MB)较大(可达几GB)
管理方式自动管理(LIFO)手动/垃圾回收管理

具体表现

值传递(简单类型)

let a = 10; // 值直接存储在栈中
let b = a;  // 创建新的栈空间,复制值
b = 20;
console.log(a); // 10 (不受影响)

引用式赋值(复杂类型)

const obj1 = { name: "Alice" }; // 对象在堆中,栈存储引用地址
const obj2 = obj1; // 复制引用地址,指向同一个堆对象

obj2.name = "Bob";
console.log(obj1.name); // "Bob" (共享同一对象)

const 与内存

对于 const 变量:

  • 简单类型:栈中的值不可变
  • 复杂类型:栈中的引用地址不可变,堆中的内容可变
const arr = [1, 2, 3];
arr.push(4); // 允许,修改堆内容
// arr = [5, 6]; // 报错,试图改变栈中的引用地址

块级作用域与循环

const 在循环中的表现:

// 每次迭代创建新的块级作用域和新的const变量
for (const i = 0; i < 3; i++) {
  // 报错,因为i++试图修改const变量
}

// 正确用法
for (const value of [1, 2, 3]) {
  console.log(value); // 每次迭代创建新的const value
}

// let 是更合适的循环计数器选择
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // 0, 1, 2 (每个i有独立作用域)
}

与 var 的区别

  1. 全局属性

    var a = 1;
    console.log(window.a); // 1
    
    const b = 2;
    console.log(window.b); // undefined
    
  2. 重复声明

    var x = 1;
    var x = 2; // 允许
    
    const y = 1;
    // const y = 2; // 报错
    
  3. 作用域

    • var:函数作用域或全局作用域
    • const:块级作用域

最佳实践

  1. 默认使用 const,只有需要重新赋值时才用 let
  2. 避免使用 var,除非有特殊需求
  3. 对于复杂对象,如果确实需要完全不可变,可以使用 Object.freeze()
    const obj = Object.freeze({ prop: 1 });
    // obj.prop = 2; // 静默失败或严格模式下报错
    

理解 const、TDZ、调用栈和调用堆的概念,有助于编写更可预测、更健壮的 JavaScript 代码,特别是在大型应用开发中。