内存泄露

内存泄露

一、4种常见的内存泄露

  • 浏览器垃圾回收机制,但并不是说我们就可以完全不用关心垃圾回收这块了,我们的代码中依然要主动避免一些不利于引擎做垃圾回收操作,因为不是所有无用对象内存都可以被回收的,那当不再用到的内存,没有及时回收时,我们叫它 内存泄漏

1.1.意外的全局变量

未声明的变量-window全局变量

当我们在一个函数中给一个变量赋值,但是没有声明它时

function Fn(){
	a = "hello";
}

此时这个全局变量a相当于是window对象下的一个变量:

function Fn(){
	window.a = "hello";
}

注:全局变量很难被垃圾回收器回收,所以会进行内存泄露。

使用this创建的变量还有种情况

function Fn(){
	this.a = "hello";
}
Fn();

当然,这个this也是指向的window,因为此时创建的a变量会挂载到window下

解决方式是:

  • 在JS文件头部或者函数的顶部加上严格模式 use strict 然后this的指向为undefined,这样就可以避免。

注:如果必须使用全局变量存储大量数据,确保用完之后把它设置为null或者重新赋值定义。

1.2.被遗忘的计时器,或回调函数

定时器引起

当我们在代码中使用定时器也有可能会造成内存泄漏

var serverData = loadData();
setIntervalDemo = setInterval(function(){
	var renderer = document.getItemById('renderer');
	if(renderer){
		render.InnerHTML = JSON.stringify(serverData)
	}
},5000)

  • 上面的例子中,节点rederer引用了serverData,在节点renderer或者数据不再需要时,定时器依旧指向这些数据。
  • 哪怕当renderer节点被移除过后,定时器interval依旧存活,所以垃圾回收器没办法回收,那么他的依赖也没法回收。

解决方式是:

  • 终止定时器。clearInterval(setIntervalDemo)

对象观察者

还有个就是关于观察者模式的案例:

var btn - document.getElementById('btn');
function onClick(element){
	element.innerHTML = 'I`m innerHTML';
}
btn.addEventListener('click',onClick);

一旦他们不再需要(或者关联的对象变成不可达),明确的移除他们非常必要。因为他要一直阻塞在那里监听,浪费内存。

解决方法:

  • 除了现在新浏览器的垃圾回收算法可以移除,建议还是把它写上为好
btn.removeEventListener('click',onClick);

1.3.脱离DOM的引用

如果把DOM存成字典(JSON键值对)或者数组,此时同样的DOM元素存在两个引用:

  • 一个在DOM树中
  • 另一个在字典中

那么两个都需要删除

例如:

//在对象中引用DOM
var elements = {btn:document.getElementById('btn')}
function doSomeThing(){
    elements.btn.click();
}
function removeBtn(){
    //将body中的btn移除,也就是移除DOM树中的btn
    document.body.removeChild(document.getElementById('button'));
    //但是此时全局变量elements还是保留了btn的引用,并且还赋值给了这个字典,这个DOM元素还是在内存中,不能回收。
}

解决方式:

  • 手动设置空指针移除:element.btn = null;

1.4.闭包

闭包,也就是局部变量销毁时
首先我们明确一点,闭包的关键就是匿名函数能够访问父级作用域中的变量

function fn(){
	var a = 'I am a';
	return function(){
		console.log(a);
	}
}
//因为a被匿名函数所引用,所以这种变量不会被回收

如果情况复杂一点

var globalVar = null;//全局
var fn = function(){
	var originVal = globalVar;//局部变量
	var unused = function(){
		if(originVal){
			console.log('call')
		}
	}
	globalVar = {
		longStr:new Array(1000000).join('*'),
		someThing:function(){
			console.log('someThing')
		}
	}
}
setInterval(fn,100)

以上案例你会发现:

  • 每次调用fn函数时候都会产生一个新对象originVal
  • 变量unused是一个引用了originVal的闭包
  • unused虽然未被使用,但是它引用的originVal迫使它留在内存中,并且不会回收

解决方式:

  • 每次执行完对originVal进行销毁,也就是赋值空指针

二、内存泄漏的识别方法

  • Chrome浏览器的控制台Performance或Memory
  • Node提供的process.memoryUsage方法

2.1.Chrome浏览器的控制台Performance或Memory

相关参考链接

Chrome 浏览器查看内存占用,按照以下步骤操作。

在网页上右键, 点击“检查”打开控制台(Mac快捷键option+command+i);选择Performance面板(很多教材中用的是Timeline面板, 不知道是不是版本的原因);勾选Memory, 然后点击左上角的小黑点Record开始录制;点击弹窗中的Stop结束录制, 面板上就会显示这段时间的内存占用情况。

2.2.Node提供的process.memoryUsage方法

console.log(process.memoryUsage());
// { rss: 27709440,
//  heapTotal: 5685248,
//  heapUsed: 3449392,
//  external: 8772 }

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。
该对象包含四个字段,单位是字节,含义如下:

rss(resident set size):所有内存占用,包括指令区和堆栈。heapTotal:"堆"占用的内存,包括用到的和没用到的。heapUsed:用到的堆的部分。external: V8 引擎内部的 C++ 对象占用的内存。

判断内存泄露, 是看heapUsed字段.

三、总结

3.1.内存泄漏包括:

  • 意外的全局变量,直接赋值,或者通过this绑定到window的全局变量
  • 被遗忘的计时器,或者回调函数或者监听器(相当于是局部作用域链的全局变量,因为这些被遗忘的里面要使用该变量)
  • 脱离DOM的引用(DOM树、字典中)
  • 闭包中重复创建的变量

3.2.避免内存泄漏:

  • 注意程序逻辑,避免死循环。
  • 减少不必要的全局变量,或者相对作用域链上的全局变量,或者生命周期很长的对象,即时回收。
  • 避免频繁创建过多的对象,用完了记得销毁。

转自:
【JavaScript】4种常见的内存泄露

posted @ 2022-03-18 23:35  黄哈哈。  阅读(86)  评论(0编辑  收藏  举报