一、什么是内存泄露
简单地说内存泄露就是在一块代码段里,不再使用的变量并没有及时释放清理掉,这就叫做内存泄露。内存泄漏程序还可以运行。
二、为什么会产生内存泄露
JS里的对象已经没有办法再去访问但是垃圾回收器还认为该对象被引用,所以垃圾回收器无法回收该对象,就会产生内存泄露。严重的内存泄露会导致内存溢出,使程序不能正常运行。
三、什么是内存溢出
当程序运行需要的内存超过剩余的内存,就会产生内存溢出,此时程序已经不能正常运行。
var obj = {}
for(var i = 0; i<10000; i++){
obj[i] = new Array(1000000) //obj是一个对象,只不过内部属性值为0.1.2.3...类似于伪数组
}
四、产生内存泄露的几种常见现象
4.1 意外的全局变量
function f(){
a = 3 //实际是window.a
console.log(a)
}
f() //f调用之后a不会立即释放,如果是局部变量a会立即释放
this也会引起内存泄露
function f(){
this.a = 'liming'
}
f() //普通调用,this指向window
在 JavaScript 文件头部加上 'use strict'
,可以避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量。
4.2 没有及时清理的定时器或回调函数
setInterval(function(){
var arr = new Array(1000000) //定时器用完没有及时清理,这块内存不会立即释放
},1000)
//正确做法
var intevalId = setInterval(function(){
var arr = new Array(1000000)
},1000)
clearInteval(intevalId) //及时关闭定时器
4.3 闭包
有时候闭包能延长局部变量的生存时间是一件好事,但有时候不是,这也会导致内存泄漏,因此用完要立即置空。
function f(){
name = 'liming'
function f1(){
console.log(name) //闭包产生
}
return f1
}
var f2 = f() //f1不会立即销毁,因为有f2指向它
f2()
f2 = null //用完闭包手动置空,不然不能被回收
4.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.1 标记清除法(mark-and-sweep)
创建变量时,垃圾回收机制,会将这个变量标记为进入环境,当变量离开环境时(比如函数调用完毕),标记为离开环境
每隔一段时间垃圾回收器会遍历所有上下文中的变量,以及被在上下文中的变量引用的变量(闭包),将他们的标记删除
剩余的仍然被标记的变量就是没有用的变量,垃圾回收器会删除它们的值,回收他们的内存
function test(){
var a = 10; //被标记"进入环境"
var b = "hello"; //被标记"进入环境"
}
test(); //执行完毕后之后,a和b又被标记"离开环境",被回收
5.2 引用计数法
当申明一个变量并且将它赋值给一个引用类型的值时,会将这个值加1,当该值赋值给另一个变量时会再加1,当该值被其他值取代时,减1,如果该值为0,说明无法访问该值了,垃圾回收机制回收。
const arr = [1,2,3,4];
console.log("hello world");
上面代码声明了一个arr数组,它的引用次数为1,但后面变量没有用到arr,他的引用次数永远是1,这样就会造成内存泄漏。应该手动置空
let arr = [1,2,3,4];
console.log("hello world");
arr = null;
六、如何解决内存泄漏
1、第一个思路就是及时解除对一些变量的引用,如手动置空、清理定时器;代码编写规范
2、weakMap、weakSet