JS 的垃圾回收主要有两种方式:
-
标记清除(Mark-and-Sweep):
这是最常用的垃圾回收方式。它分为两个阶段:
- 标记阶段: 垃圾回收器从根对象(例如全局对象)开始遍历,标记所有可访问的对象。可访问的对象指的是那些仍然被引用的对象。
- 清除阶段: 垃圾回收器遍历堆内存,清除所有未被标记的对象,也就是那些不可访问的对象,释放它们占用的内存。
优点: 实现简单,可以有效回收循环引用导致的内存泄漏。
缺点: 清除阶段需要遍历整个堆内存,效率较低;清除后会产生内存碎片,导致分配大块内存时效率降低。
-
引用计数(Reference Counting):
这种方式不太常用,因为它无法处理循环引用。它的原理是:
- 每个对象都有一个引用计数器。
- 当一个对象被引用时,计数器加 1。
- 当一个对象不再被引用时,计数器减 1。
- 当计数器为 0 时,对象被回收。
优点: 回收及时,不会造成明显的卡顿。
缺点: 无法处理循环引用,即使循环引用的对象不再被外部引用,它们的引用计数也不会为 0,导致内存泄漏。
现代浏览器通常采用标记清除算法或其变种,例如:
- 标记整理(Mark-Compact/Sweep-Compact): 在标记清除的基础上,将存活的对象移动到内存的一端,消除内存碎片,提高内存分配效率。
- 增量式标记清除(Incremental Mark-and-Sweep): 将标记清除过程分成多个小步骤,穿插在 JavaScript 代码执行过程中,减少垃圾回收造成的卡顿。
- 分代式垃圾回收(Generational Garbage Collection): 将对象分为新生代和老生代,对新生代对象进行更频繁的垃圾回收,因为新生代对象通常生命周期较短,可以提高效率。
总而言之,理解 JS 垃圾回收机制有助于编写更高效的代码,避免内存泄漏。 虽然开发者不需要手动管理内存,但了解这些机制可以帮助你更好地理解代码的性能表现。