一、内存泄露
不再使用的内存,没有及时被释放,机会造成内存泄露
二、垃圾回收机制
因为收回过程内存开销比较大,且堵塞其他操作,所以V8引擎会周期性的释放那些不再使用的变量,进而释放内存
1. 全局变量:生命周期较长,直到卸载当前页面才会回收
2. 局部变量:如函数内的变量(排除个别闭包),一旦函数执行完毕,该变量即会被回收
2. 闭包:分为两种,一种是程序自动生成的闭包,会遵循局部变量规则,但手动生成的闭包,V8引擎不会自动回收,需要开发者人为的将变量置为null,才会被回收。后期单独讲解闭包
三、方式
主要分为两种,标记清除和引用计数
1. 标记清除:
变量进入执行上下文时,会打个进入环境的标记,当离开执行环境时,再打个离开环境的标记,在下一个回收周期内,会将离开执行环境的变量进行回收
2. 引用计数:
当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。
相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。如:
代码语言:javascript复制function fn() {
var q = {}; // q 指向对象的引用次数为 1
var a = q; // q 指向对象的引用次数加 1,为 2
var c = q; // q 指向对象的引用次数再加 1,为 3
var a = {}; // q 指向对象的引用次数减 1,为 2
}
这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存,进而无法刚问引用次数为0的变量。
但该方式会存在一个bug,即循环引用的变量不会被回收,如:
代码语言:javascript复制function fn() {
var q = {}; // q 指向对象的引用次数为 1
var a = {}; // a 指向对象的引用次数为 1
q.obj = a; // a 指向对象的引用次数为 2
a.obj = q; // q 指向对象的引用次数为 2
}
fn();
即使fn已经执行完毕,理论上q和a应该会被回收,但由于引用次数不为0,就造成了不会被回收
四、哪些场景会造成内存泄露
- 不合理使用闭包,内部有外部变量引用的话,那么这个变量就一直在内存中无法回收
- 不合理使用定时器,已结束使用定时器,但未取消,且定时器内部有外部变量引用的话,那么这个变量就一直在内存中无法回收
- 不合理获取DOM元素,DOM已经被删除,且程序一直保存该DOM的引用,导致无法被回收