Skip to content

JavaScript 内存(栈和堆)

约 1294 字大约 4 分钟

JavaScript

2025-01-07

内存管理概述

JavaScript 的内存管理主要由其引擎(如 V8 引擎)自动完成,开发者无需手动管理内存。但了解其背后的机制,还是很有必要的。

内存管理是指对程序运行过程中分配、使用和释放内存的控制。在 JavaScript 中,这一过程主要包括以下三步:

  • 分配内存:在变量或对象声明时,JavaScript 引擎会为其分配内存。
  • 使用内存:读取或写入变量时,会从分配的内存中操作数据。
  • 释放内存:当变量不再被引用时,JavaScript 的垃圾回收机制会回收这部分内存。

栈和堆的基础知识

JavaScript 使用两种主要的数据结构来管理内存:栈(Stack) 和 堆(Heap)。两者各自承担不同类型的内存存储任务。

1. 栈(Stack)

是一种自动分配内存的结构,用于存储原始数据类型(Primitive Types)和函数调用中的执行上下文。栈具有**后进先出(LIFO,Last In First Out)**的特性。

栈的特点:

  • 内存分配快速:栈中的数据大小固定,分配时性能高。
  • 作用域清晰:栈中的变量在作用域结束后自动销毁。
  • 存储基本数据类型:如 numberstringbooleannullundefinedsymbol
function sum(a, b) {
    let result = a + b; // result 是存储在栈中的变量
    return result;
}
let total = sum(5, 10); // total 也存储在栈中

上面的代码中a、b 和 result 都是原始数据类型,被分配到栈中,在函数 sum 执行结束后,这些变量会从栈中移除。

栈内存的操作:

  • 当函数调用时,会创建一个新的执行上下文,并将变量分配到栈中。

  • 当函数执行完毕,该执行上下文会从栈中弹出,释放所有内存。

堆(Heap)

堆是一种动态分配内存的结构,主要用于存储引用数据类型(Reference Types),例如对象、数组和函数。堆内存可以存储更复杂和动态的数据。

堆的特点:

  • 内存分配灵活:堆的大小可以动态调整,适合存储复杂数据结构。
  • 访问速度较慢:堆内存的分配和回收都比栈慢,因为需要进行更复杂的操作。
  • 存储引用数据类型:如对象、数组和函数。
let person = {
    name: "John",  // name 是存储在堆中的属性
    age: 30        // age 也是堆中的属性
};

let numbers = [1, 2, 3]; // 数组对象也存储在堆中

对象 person 和数组 numbers引用类型,它们被分配到堆内存。

栈中保存的是这些对象的引用地址,而非实际数据。

栈与堆的对比

特性栈(Stack)堆(Heap)
数据类型原始数据类型引用数据类型
内存分配自动分配大小固定动态分配,大小不固定
访问速度较慢
内存管理由 JavaScript 引擎自动管理依赖垃圾回收机制
作用域结束自动销毁当引用计数为 0 时才会销毁

栈与堆的工作机制

栈的运行机制:后进先出(LIFO)

function example() {
    let x = 10; // x 入栈
    let y = 20; // y 入栈
    return x + y; // 函数执行完毕,x 和 y 出栈
}
example();
  • 步骤 1:调用 example(),创建执行上下文,将 x 和 y 存储在栈中。
  • 步骤 2:函数返回结果后,执行上下文销毁,x 和 y 出栈。

堆的运行机制:动态分配

堆中的对象通过栈中的引用进行访问。当对象不再被引用时,会被垃圾回收。

let obj1 = { name: "Alice" }; // obj1 指向堆内存中的对象
let obj2 = obj1;             // obj2 也指向同一个对象
obj1 = null;                 // obj1 的引用被清除,但对象仍被 obj2 引用
  • 步骤 1:obj1 和 obj2 引用同一个堆内存中的对象。
  • 步骤 2:obj1 被赋值为 null,但 obj2 仍指向该对象,因此对象不会被销毁。

栈与堆的常见问题

1. 栈溢出(Stack Overflow)

当函数调用过深或递归未正确终止时,可能导致栈溢出。所以在写递归的时候确保递归有明确的终止条件。

function recursive() {
    return recursive(); // 无限递归
}
recursive(); // 会导致栈溢出

2. 堆内存泄漏(Memory Leak)

当堆中的对象无法被垃圾回收时,会导致内存泄漏。


// 全局变量未清理:

let globalObject = {};

// 事件监听器未移除:
element.addEventListener("click", () => {
    console.log("clicked");
});

优化内存管理的建议

  1. 减少全局变量:避免使用过多的全局变量,使用局部作用域管理变量。
  2. 手动清除引用:当对象不再需要时,显式设置为 null
let obj = { key: "value" };
obj = null; // 手动清除引用
  1. 避免闭包滥用:闭包会导致变量被长时间保留,尽量避免不必要的闭包。

  2. 移除事件监听器:在元素被销毁前移除事件监听器。

element.removeEventListener("click", handler);

变更历史

最后更新于: 查看全部变更历史
  • feat: 添加“两数之和”算法文档

    于 2025/4/23

获得简单的快乐应该稀释烦恼