JavaScript内存泄漏原理与常见场景解析
本文深入解析JavaScript中的内存泄漏问题,介绍标记清除与引用计数两种垃圾回收机制。重点分析意外全局变量、定时器未清理、闭包引用及DOM节点残留等常见泄漏场景,并提供相应的解决方案,帮助开发者优化内存管理。
一、内存泄漏是什么?
内存泄漏(Memory leak)是计算机科学中的一个概念,指由于程序疏忽或设计缺陷,导致已分配的内存无法被释放。
这并非指物理内存的消失,而是应用程序在分配内存后,因逻辑错误导致在释放前失去对该内存的控制权,从而造成资源浪费。
程序运行依赖内存支持,只要程序发出请求,操作系统或运行时环境就必须提供相应的内存空间。
对于长期运行的服务进程,必须及时释放不再使用的内存。否则,随着内存占用不断攀升,轻则拖慢系统性能,重则引发进程崩溃。

在C语言等手动管理内存的语言中,内存泄漏是常见现象。
char * buffer;
buffer = (char*) malloc(42);
// Do something with buffer
free(buffer);例如在C语言代码中,使用malloc方法申请内存后,必须显式调用free方法进行释放。
这种手动管理较为繁琐,因此大多数现代语言提供了自动内存管理机制,即“垃圾回收机制”,以减轻开发者的负担。
二、垃圾回收机制
JavaScript内置了自动垃圾回收机制(GC:Garbage Collection),执行环境会自动管理代码执行期间使用的内存。
其核心原理是:垃圾收集器定期(周期性)扫描并识别不再使用的变量,随后释放其占用的内存。
目前主要有两种实现方式:
- 标记清除
- 引用计数
1、标记清除
这是JavaScript中最常用的垃圾回收策略。
当变量进入执行环境时,会被标记为“进入环境”,此时其占用的内存不能被释放;当变量离开执行环境时,则被标记为“离开环境”。
垃圾回收程序运行时,会标记内存中存储的所有变量,随后移除所有在当前上下文中的变量,以及被当前上下文变量引用的变量的标记。
经过上述步骤后,仍被标记的变量即为待删除对象,因为当前上下文中的任何变量都无法访问它们。
最后,垃圾回收程序执行内存清理操作,销毁所有带标记的值并回收其内存。
代码如下(示例):
var m = 0,n = 19 // 把 m,n,add() 标记为进入环境。
add(m, n) // 把 a, b, c标记为进入环境。
console.log(n) // a,b,c标记为离开环境,等待垃圾回收。
function add(a, b) {
a++
var c = a + b
return c
}2、引用计数
语言引擎维护一张“引用表”,记录内存中所有资源(通常是各种值)的引用次数。如果一个值的引用次数降为0,说明该值不再被使用,可以释放其内存。
然而,如果一个值不再需要但引用次数不为0,垃圾回收机制将无法释放该内存,从而导致内存泄漏。
代码如下(示例):
const arr = [1, 2, 3, 4];
console.log('hello world');
// arr = null上述代码中,数组[1, 2, 3, 4]作为一个值占用内存。变量arr是该值的唯一引用,因此引用次数为1。即使后续代码不再使用arr,它仍会持续占用内存。
若希望垃圾回收机制释放该内存,只需将arr设置为null,从而解除对数组[1,2,3,4]的引用,使引用次数变为0,内存即可被回收。
小结
拥有垃圾回收机制并不意味着可以完全忽视内存泄漏。对于占用空间较大的值,一旦不再使用,需检查是否仍存在引用;若存在,必须手动解除引用。
三、常见的内存泄漏的情况
1、意外的全局变量
// 不设置定义变量的方法默认为var
function foo(arg) {
bar = "this is a hidden global variable";
}
// 另一种意外的全局变量可能由 this 创建:
function foo() {
this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();使用严格模式可以有效避免意外创建全局变量。
2、定时器造成的内存泄露
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 处理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);如果id为Node的元素已从DOM中移除,该定时器仍会继续运行。同时,由于回调函数中包含对someResource的引用,导致定时器外部的someResource也无法被释放。
3、闭包
function bindEvent() {
var obj = document.createElement('XXX');
var unused = function () {
console.log(obj, '闭包内引用obj obj不会被释放');
};
obj = null; // 解决方法
}闭包会维持函数内的局部变量,导致这些变量无法被正常释放。
4、没有清理对DOM元素的引用
const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用5、事件监听(addEventListener)
在使用addEventListener添加事件监听时,若不再需要监听,应使用removeEventListener取消监听,以释放相关资源。
总结
到此这篇关于浅谈JavaScript中内存泄漏的几种情况的文章就介绍到这了,更多相关JavaScript 内存泄漏内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
