大家好,我是前端架构师,关注微信公众号【程序员大卫】免费领取前端精品资料。
背景
在 JavaScript 开发中,合理的内存管理至关重要。由于 JavaScript 具有自动垃圾回收机制,开发者通常无需手动管理内存。但如果代码中存在不必要的引用或未能正确释放内存,仍然可能导致 内存泄漏,影响程序性能,甚至引发崩溃。
本篇文章介绍了 JavaScript 的 垃圾回收机制,分析了 内存泄漏的常见原因,并通过代码示例和 Chrome DevTools 的 Memory 面板,展示如何 检测和优化 内存使用,帮助开发者编写更加高效、稳定的代码。
1. 什么是垃圾
在程序运行过程中,如果某些内存数据不再被访问(即无法触达),这些数据就被称为垃圾。
2. 什么是内存泄漏
当满足以下两种情况时,就会发生内存泄漏:
- 我们确定不再需要某块内存。
- 垃圾回收器未能回收这块内存,导致它仍然占用空间。
3. 垃圾回收机制
JavaScript 采用 标记清除(Mark-and-Sweep)算法进行垃圾回收。
其核心思想是:通过判断程序中哪些内存仍然可访问(即可触达),将无法访问的内存标记为垃圾,并将其清除。
4. 内存泄漏与闭包
- 闭包本身不会导致内存泄漏,但如果闭包持有不再需要的函数引用,则会导致相关的词法环境无法销毁,从而造成内存泄漏。
- 当多个函数共享同一个词法环境时,该环境可能无法销毁,使相关的内存无法被回收,最终引发内存泄漏。(示例见 5.3)
5. 示例
5.1 使用 FinalizationRegistry 监听垃圾回收
以下代码在几秒钟后,控制台会打印 清理 o 对象。
let o: any = {};
setTimeout(() => {
o = null;
}, 0);
const registry = new window.FinalizationRegistry((key) => {
console.log("清理", key);
});
registry.register(o, "o 对象");
如果希望立即看到日志输出,可以切换到 Performance 面板,并点击 垃圾回收(Garbage Collection) 按钮。
5.2 WeakSet 与垃圾回收
const ws = new WeakSet();
let o: any = {};
ws.add(o);
setTimeout(() => {
o = null;
}, 0);
console.log(ws.has(o)); // 垃圾尚未回收,返回 true
setTimeout(() => {
console.log(ws.has(o)); // 垃圾已回收,返回 false
}, 0);
const registry = new window.FinalizationRegistry((key) => {
console.log("清理", key);
});
registry.register(o, "o 对象");
5.3 特殊情况下的内存无法回收
通常情况下,无法触达的内存会被回收,但在某些特殊情况下可能不会。
例如,下面的代码在点击按钮后会创建 doms 数组,按理来说它无法被访问,应该被回收。然而,由于 _temp 函数引用了 doms,导致其占用的内存无法回收。
如果移除 _temp 函数,不管点击多少次按钮,doms 数组都会被正确回收。
<!DOCTYPE html>
<html>
<head>
<title>垃圾回收</title>
</head>
<body>
<button id="btn">按钮</button>
<script>
function createIncreate() {
const doms = new Array(100000).fill(0).map((_, i) => {
const dom = document.createElement("div");
dom.innerHTML = i;
return dom;
});
function increate() {}
function _temp() {
doms;
}
return increate;
}
let increate;
btn.addEventListener("click", () => {
increate = createIncreate();
});
</script>
</body>
</html>
5.4 使用 Memory 面板分析内存泄漏
可以使用 Memory 面板检查内存情况:
1.在右侧选择 Heap Snapshot。
2.点击红色按钮 Take Heap Snapshot。
第一次快照显示内存占用 11.7M。
点击按钮后,再次点击 Take Heap Snapshot,生成第二个快照,内存占用增加至 31.3M。
切换到 Comparison 标签,对比两个快照。
关键字段解释:
#New:表示在两个快照之间新创建的对象数量。#Deleted:表示在两个快照之间被删除的对象数量。#Delta:表示两个堆快照(heap snapshots)之间的对象数量变化。 计算公式:#Delta = #New - #DeletedAlloc. Size:表示新分配的内存大小(单位:字节)。
总结
文章介绍了 JavaScript 的垃圾回收机制和内存泄漏问题。垃圾指无法访问的内存数据,而内存泄漏则是本应回收的内存未被释放。JavaScript 主要采用 标记清除(Mark-and-Sweep)算法进行垃圾回收。
关键点:
- 闭包 可能导致不必要的内存占用,影响回收。
- WeakSet 与 FinalizationRegistry 可用于优化内存管理。
- 特殊引用情况 可能阻止垃圾回收,如函数保留不必要的变量。
- Memory 面板 提供
Heap Snapshot和Comparison视图,帮助分析内存变化,查找泄漏点。
本文通过示例代码和 Chrome DevTools 的调试方法,帮助开发者理解和优化 JavaScript 内存管理。
如果本文对你有帮助,欢迎点赞❤️收藏⭐,也欢迎在评论区交流!