JavaScript的内存管理机制由引擎自动完成,核心目标是合理分配内存、高效使用内存、及时回收不再使用的内存,保障程序稳定运行。整个流程分为内存分配、内存使用、内存释放三个阶段,其中内存释放依赖垃圾回收算法实现。

JavaScript内存生命周期
1. 内存分配
当声明变量、函数、对象时,JavaScript引擎会自动为对应的值分配内存空间。基础类型的值通常存储在栈内存中,引用类型的值存储在堆内存中,栈内存中仅保存指向堆内存的引用地址。
以下代码声明变量时就会触发内存分配:
// 基础类型分配栈内存
let num = 10; // 为数字10分配栈内存
let str = 'hello'; // 为字符串hello分配栈内存
// 引用类型分配堆内存,栈内存保存引用地址
let obj = { name: 'test' }; // 对象存储在堆内存,obj变量保存堆内存地址
let arr = [1, 2, 3]; // 数组存储在堆内存,arr变量保存堆内存地址
function fn() {
console.log('test');
}
// 函数也是引用类型,存储在堆内存,fn变量保存函数地址
2. 内存使用
内存分配完成后,开发者可以通过变量读写对应内存空间的值,这个阶段就是内存使用过程。基础类型直接操作栈内存中的值,引用类型通过栈中的引用地址操作堆内存中的内容。
3. 内存释放
当内存中的值不再被程序使用时,JavaScript引擎会通过垃圾回收算法自动回收这部分内存。判断值是否可用的核心标准是是否存在可达的引用,没有引用的内存空间会被标记为可回收。
核心垃圾回收算法
引用计数算法
引用计数的原理是为每个值维护一个引用计数器,当有变量引用该值时计数器加1,当引用失效时计数器减1,当计数器为0时说明该值不再被使用,直接回收对应内存。
该算法的优点是回收及时,发现计数器为0立刻回收;缺点是存在循环引用问题,两个对象互相引用时计数器永远不会为0,会导致内存泄漏。
以下代码会产生循环引用问题:
function createCycle() {
let objA = {};
let objB = {};
// 两个对象互相引用,引用计数都为2
objA.ref = objB;
objB.ref = objA;
// 函数执行结束后,objA和objB的作用域消失,但两个对象的引用计数仍为1,无法被回收
}
createCycle();
标记清除算法
标记清除是现代JavaScript引擎主流使用的垃圾回收算法,分为标记和清除两个阶段。标记阶段从根对象(如全局对象、当前执行栈中的变量)出发,遍历所有可达的对象并标记为活跃;清除阶段遍历所有内存,回收未被标记的对象的内存空间。
该算法解决了循环引用的问题,因为互相引用的两个对象如果没有被根对象可达,都会被标记为不可达,最终被回收。缺点是清除后会产生内存碎片,可能影响后续大对象的内存分配。
常见内存泄漏场景及规避方法
- 意外的全局变量:未使用let、const声明变量,或者在函数内使用this赋值未声明的变量,会创建全局变量,全局变量生命周期和页面一致,不会被回收。规避方法:使用严格模式,所有变量都显式声明。
- 未被清理的定时器:setInterval、setTimeout的回调函数中引用了外部变量,定时器未清除时,引用的变量不会被回收。规避方法:不需要定时器时及时调用clearInterval、clearTimeout清除。
- 脱离DOM的引用:将DOM元素保存在变量中,后续DOM被移除但变量仍引用该元素,元素不会被回收。规避方法:不需要引用时及时将变量赋值为null。
- 闭包的不当使用:闭包会保留外部函数的作用域,如果闭包长期存在,外部函数中的变量也不会被回收。规避方法:不需要闭包时及时释放引用,避免闭包长期存活。
理解JavaScript底层的内存管理机制,能够帮助开发者在日常开发中主动规避内存泄漏问题,写出性能更优的代码,也能更快速地定位程序运行中的内存相关问题。
JavaScript内存管理垃圾回收引用计数标记清除修改时间:2026-07-07 02:57:25