JavaScript 内存管理与垃圾回收机制详解

作者:异步漫步

时间:2026-10-27

浏览:69

本文深入解析JavaScript的内存管理机制,涵盖栈与堆的存储差异、调用栈工作原理。重点讲解引用计数法与标记清除法,以及现代V8引擎采用的分代收集策略(新生代Scavenge与老生代Mark-Sweep)。同时列举全局变量、定时器、闭包等常见内存泄漏场景,并提供解除引用、使用WeakMap等优化方案,帮助开发者提升前端性能。

文章目录

一、内存的储存和代码执行的场所关系

对于任何编程语言而言,内存管理与垃圾回收都是进阶开发者必须掌握的核心知识。在面试中,面试官常会询问关于前端内存处理的细节,例如:你对JS的垃圾回收机制了解多少?哪些情况会导致内存泄漏?

1. 储存空间

JavaScript主要涉及两种存储结构:

  • 栈空间(Stack)
  • 用于存储原始类型数据(Primitive Types)。
  • 执行上下文(Execution Context)
  • 堆空间(Heap)

为什么不将所有数据都存储在栈中?

栈空间主要用于维护程序执行期间的上下文状态。如果栈空间过大,将所有数据(包括大型对象)都存放在栈中,会显著降低上下文切换的效率,进而影响整个程序的执行性能。因此,引用类型数据通常存储在堆中,栈中仅保存指向堆的指针。

2. 内存的生命周期

内存生命周期包含三个阶段: 1. 内存分配:在声明变量、定义函数或创建对象时发生。 2. 内存使用:通过读写内存、调用变量或函数进行交互。 3. 内存回收:当数据不再被使用时,由垃圾回收机制自动释放。

3. js 中的内存分配和使用

// 分配
const num = 123;  // 分配到栈
const str = 'sss';// 分配到栈
const obj = {};   // 分配到堆
// 使用
const a = 10;
console.log(a); // 使用

4. 调用栈下移ESP(记录当前执行状态的指针)

当一个函数执行完毕后,JavaScript 引擎会通过向下移动栈指针(ESP)来销毁该函数在栈中保存的执行上下文。以下示例展示了通过 ESP 状态变化的过程:

var foo1 = () => {
    console.log('foo1')
    foo2()
}
var foo2 = () => {
    console.log('foo2')
}
foo1()

一、内存的储存和代码执行的场所关系

当 ESP 指针移开函数作用域 foo1 后,该作用域明显不再被引用,随后将直接被垃圾回收器(GC)回收。

二、 js中的垃圾回收机制

有 C 语言经验的开发者一定熟悉,在内存声明分配后,需要手动执行 free 操作,即手动回收内存。而 JavaScript 采用自动内存管理机制,开发者无需过多关注内存的分配与释放,这些工作均由 V8 引擎中的垃圾回收器(GC)负责。

早期接触 JavaScript 时,关于内存管理和垃圾回收主要介绍以下两种概念:

1. 引用计数法(Reference Counting)

引用计数法依赖引用的概念,该机制最早在 IE 浏览器中使用。其核心逻辑是:判断一个对象是否有指向它的引用,如果没有其他对象再指向它,说明该对象不再被需要,可以回收。

该方法的主要缺陷在于:循环引用(Circular Reference)。

如果两个对象相互引用,即使它们已不再被程序使用,引用计数也无法归零,导致内存无法释放,从而引发内存泄漏。

2. 标记清除法(Mark-Sweep)

将“不再使用的对象”定义为“无法到达的对象”(Unreachable Objects)。

从根节点(JavaScript 的全局对象)出发,定时递归扫描内存中的对象。凡是无法从根节点到达的对象,都会被标记为不再使用,随后进行回收。

执行过程如下:

  • GC 运行时,会给内存中的所有变量加上标记。
  • 从根节点触发,能够触及到的对象会被清除标记。
  • 剩余仍有标记的变量被视为准备删除的变量。
  • GC 销毁带有标记的值,回收内存空间。

三、代际假说和分代收集

代际假说(The Generational Hypothesis)是现代浏览器垃圾回收策略的基础。整个模型可参考下图:

一、内存的储存和代码执行的场所关系

新生代(副垃圾回收器)

存放生存时间短、占用空间较小的对象。通过 Scavenge 算法,将新生代空间对半划分为两个区域:一半是对象区域,一半是空闲区域。新对象放入对象区,当对象区快满时,将存活的对象复制到空闲区,随后角色互换。同时执行对象晋升策略:在对象区域和空闲区域翻转两次后仍存在的对象,将升级到老生代。这个复制翻转的过程也有助于避免内存碎片的产生。

老生代(主垃圾回收器)

存放生存时间久或体积较大的对象,使用标记清除算法进行垃圾回收。一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。这种行为称为全停顿(Stop-The-World)。实际上,浏览器为了避免垃圾回收导致的卡顿,通过增量标记方式将回收任务拆解为多个小任务,穿插在 JS 主线程中执行。

四、常见的内存泄漏

1. 全局变量


function foo() {
    bar1 = 'aaa'; // 相当于声明在window.bar1
    this.bar2 = 'aaaa'
}
foo(); // 执行函数事this指向window ,相当于一个函数给全局变量增加了两个变量

2. 未被清理的定时器和回调函数

//setInterval
//setTimeout
setInterval(() => {
    console.log('test')
}, 500)
//没用用 clearInterval clearTimeout 做清除

3. 闭包

个人最喜欢《你不知道的JS》里对闭包的描述:

一个内部函数,有权访问包含其的外部函数的变量 —— 《你不知道的js》 或者也可以用“内存逃逸”这种高逼格的术语形容。

// 闭包 gc 案例
var one = null;
var replace = function() {
    var originalOne = one;
    var unused =function() {
        if(originalOne) {
            console.log(111);
        }
    }
    one = {
        longString: '111',
        method: function() {
            console.log()
        }
    }    
}
setInterval(replace, 500)

每次调用 replace, one 得到一个包含字符串和一个对于新闭包 method 的对象 unused 引用了 originOne

5. DOM 引用

var elements= {
    image: document.getElementById('111');
}
elements.image = null;

6. 怎么避免呢?

  • 尽量减少全局变量的使用。
//尽可能少写
window.object = {} // 这类代码
  • 使用完引用数据后,及时解除引用,将其赋值为 null。
let obj = {}
...
obj = null;
  • 避免死循环等持续执行的操作(例如边界判断不清晰的 for 或 while 循环)。
  • 多使用 WeakSet / WeakMap 特性,利用其弱引用特性避免内存泄漏。
// Vue3 中就大量用了WeakMap 优化实例引用
const bucket = new WeakMap();
...
const obj = new Proxy(data, {
  get(target, key) {
    track(target, key);
    return target[key];
  },
  set(target, key, newVal) {
    target[key] = newVal;
    trigger(target, key);
  },
});
function track(target, key) {
  if (!activeEffect) return;
  let depsMap = bucket.get(target);
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()));
  }
  // 再根据key 从 depsMap 中取得 deps, 它是一个 Set 类型,里面存储着左右与当前 key 相关联的副作用函数: effects
  let deps = depsMap.get(key);
  // 如果 deps 不存在,同样新建一个 Set 并与 key 关联
  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }
  deps.add(activeEffect);
  activeEffect.deps.push(deps);
}
...

参考

【1】 《浏览器工作原理与实践》

【2】 一文搞懂垃圾回收

以上就是JS前端的内存处理的方法全面详解的详细内容,更多关于JS前端内存处理的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章