javascript的垃圾回收机制

x33g5p2x  于2022-02-14 转载在 JavaScript  
字(2.5k)|赞(0)|评价(0)|浏览(238)

最近看了一下javascript垃圾回收机制的博客,现在对其进行简单的总结。
垃圾回收:是一个自动的垃圾回收机制
垃圾回收的原理:考虑某一些对象或者变量在未来运行中不被考虑,并且向这些对象要求归还内存。但是在垃圾回收中,最为困难的就是找到在未来不被使用的对象。
为了解决上述问题,我们可以采用以下两种方法、
一、引用计数法
如果对象a中存在对对象b的引用,则对象b上的引用数不为0,此时在垃圾回收时,不能进行销毁。

let objA = {name:"dmc"}   //此时{name:"dmc"}的引用次数为1
let objB = objA          //此时{name:"dmc"}的引用次数为2.

objA = 0   //此时{name:"dmc"}的应用此时为1
objB = 0   //此时{name:"dmc"}的引用次数为0
//此时当垃圾回收时,可以回收{name:"dmc"}的内存。

但是引用计数存在一个弊端,就是存在循环引用的问题。

function func1() {
 	let objA = {}
 	let objB = {}
 	objA.b = objB
 	objB.a = objA
}

正如上面的代码所示,此时存在一个函数,当函数执行完毕后,此时垃圾回收应该进行销毁操作,但是此时objA和objB两个都存在一个引用,此时这两个对象不能进行销毁。解决方法是:当对象使用完毕后,应该设置变量为null。
二、标记清除法
在javascript中最常用的垃圾回收方式是标记清除法,因为从2012年起,所有现代浏览器都使用标记清除的垃圾回收方法,除了低版本的IE采用的是引用计数法。那么什么是标记清除法,就是一个全局对象出发,在浏览器中是window,从中去查找全部可以访问到的对象并进行标记,这是标记的过程。然后是清除,就是对没有标记的对象进行清除,也就是清除那些不可达的对象。标记清除法存在一个弊端,就是我们清理垃圾后,得到的内存不是连续的空间,但是如果我们在某一时刻,需要大量连续的内存空间,该如何做?此时就涉及在标记清除法的基础上的另一种的垃圾回收方法——标记整理法,就是我们在进行垃圾处理的过程中,会对内存空间进行整理。但是这样的效率比标记清除法低,在计算机中很多做法都是相互妥协的结果。
三、内存泄漏
内存泄漏:内存泄漏是指计算机可用的内存越来越少,主要是因为程序不能释放那些不能使用的内存。下面我们总结一下内存泄漏的几点原因:
1、循环引用
上面我们提到到循环引用就是两个对象之间相互引用对方,导致内存无法释放。再次强调,一旦数据不使用,我们应当将其赋值为null。
2、无意的全局变量

function foo() {
	const lov = "dmc"
}

上面foo函数我们在执行时会创建一个函数作用域,当函数执行完毕后,此时该函数作用域(其实是一个对象)会被销毁。这是正常的情况。下面我们看一下创建无意的全局变量。

function foo(){
	lov = "dmc"
}

上面foo函数在执行时不仅会创建一个函数作用域,并且由于lov变量没有声明,此时会在window上进行定义,所以window.lov="dmc",当函数执行结束后,此时window全局对象上的lov属性并不能清除,此时造成内存泄漏。

function foo(){
	this.lov = "dmc"
}

如上面代码所示,此时当函数执行时,this指向window
3、被忽略的计时器和回调函数

let someResource = getData()
setInterval(() => {
	const node = document.getElementById("node")
	if(node) {
		node.innerHTML = JSON.stringify(someResource)
	}
}, 1000)

上面的例子中,我们使用定时器,此时并且在定时器回调函数中使用外部变量,此时我们在进行操作时,每一个回调函数都会产生对其的引用,并且在执行定时操作时,回调函数以及其内部的变量都不会被销毁。此时造成内存泄漏,当我们不使用定时操作时,可以使用clearInterval来删除定时器。
4、无用DOM 元素的引用
在IE8以下版本的浏览器,DOM对象通常回合javascript之间产生循环引用的关系。下面这个例子。

function handlerSet() {
	let ele = document.getElementById("ele")
	ele.onclick = function() {
		console.log("在这里执行相关操作")
	}
}

上面的ele通过onclick来引用函数,函数存在外部引用来引用这ele对象,此时造成循环引用,造成内存泄漏。不过现在不需要担心这种情况。因为现在浏览器不使用引用计数法,采用的是标记整理法来进行垃圾回收。
下面我们在看一个例子:

const btn = document.querySelector("button")
document.body.removeChild(document.querySelector("button"))

此时我们以为已经删除该DOM元素,其实不然,因为我们在处理任然存在一个变量指向该DOM元素。此时我们应该设置btn = null
还存在一种情况:如果我们存在列表元素ul和li,此时如果我们取出其中的一个标签li,并且删除ul,此时整个列表不会删除,因为li和其父元素ul标签至今存在引用关系。由此导致内存泄漏。
5、不合理的闭包导致内存泄漏
四、良好的实践
优化内存最好的方式是保留内存中仍然使用的数据,将不需要的数据设置为null
减少内存的方法就是创建对象,例如const obj = {}const arr = []都为创建对象,如果我们不需要使用某一个数组时,可以将数组设置为[].length = 0

相关文章