(一)垃圾回收机制
1. 垃圾回收机制都有哪些策略?⭐⭐⭐⭐⭐
(1)标记清除法
- 当变量进入上下文(比如在函数内部声明一个变量时),这个变量会被加上存在于上下文中的标记
当变量离开上下文时,也会被加上离开上下文的标记。 - 垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后再访问这些对象并标记它们的引用…
如此递进结束后若发现有没有标记的(不可达的)进行删除, - 进入执行环境的不能进行删除
(2)引用计数法(被标记清除取代)
其思路是对每个值都记录它被引用的次数,
- 当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,
当该值赋值给另一个变量的时候,该计数+1,
当该值被其他值取代的时候,该计数-1,
当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象 - 缺点: 当两个对象循环引用的时候,引用计数无计可施。如果循环引用多次执行的话,会造成崩溃等问题。所以后来被标记清除法取代
(二)内存泄漏
1. 什么是内存泄漏⭐⭐⭐⭐⭐
引擎中有垃圾回收机制,它主要针对一些程序中不再使用的对象(基本类型会自动被回收),对其清理回收释放掉内存。
内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用
2. 为什么会导致的内存泄漏⭐⭐⭐⭐⭐
- 简单一句话,该被清除的变量没被清除;
- 内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃
3. 导致内存泄漏的情况
(1) 不正当的使用闭包
- 返回函数引用了所在函数作用域的变量
- 减少闭包的使用,在函数调用后,应及时把外部的引用关系置空
function fn2(){
let test = new Array(1000).fill('isboyjc')
return function(){
console.log(test)
return test
// `return` 的函数中存在函数 `fn2` 中的 `test` 变量的引用,
// 所以 `test` 并不会被回收,也就造成了内存泄漏
}
}
let fn2Child = fn2()
fn2Child()
fn2Child = null // 及时把外部的引用关系置空
(2) 隐式全局变量
- 对于全局变量,垃圾回收器很难判断这些变量什么时候才不被需要,所以全局变量通常不会被回收
- 在使用完将其置为
null即可
function fn(){
// 没有声明从而制造了隐式全局变量test1
test1 = new Array(1000).fill('isboyjc1')
// 函数内部this指向window,制造了隐式全局变量test2
this.test2 = new Array(1000).fill('isboyjc2')
}
fn()
(3)游离DOM引用
-
当我们使用变量缓存 DOM 节点引用后删除了节点,如果不将缓存引用的变量置空,依然进行不了 GC,也就会出现内存泄漏
-
考虑到性能或代码简洁方面,我们代码中进行 DOM 时会使用变量缓存 DOM 节点的引用,但移除节点的时候,我们应该同步释放缓存的引用,否则游离的子树无法释放。
<div id="root">
<ul id="ul">
<li></li>
<li></li>
<li id="li3"></li>
<li></li>
</ul>
</div>
<script>
let root = document.querySelector('#root')
let ul = document.querySelector('#ul') // ul变量引用ul的子节点
let li3 = document.querySelector('#li3') // li3变量引用ul的子节点
root.removeChild(ul)// 由于ul变量存在,整个ul及其子元素都不能GC
ul = null // 虽置空了ul变量,但由于li3变量引用ul的子节点,所以ul元素依然不能被GC
li3 = null // 已无变量引用,此时可以GC
</script>
(4)遗忘的定时器
let func = getData()// 获取数据
setInterval(() => {
const node = document.getElementById('Node')
if(node) {
node.innerHTML = JSON.stringify(func))
}
}, 1000)
- 上例中,在
setInterval没有结束前,回调函数里的变量以及回调函数func本身都无法被回收。 - 只有调用了
clearInterval才算结束。如果没有被clear掉的话,就会造成内存泄漏。不仅如此,如果回调函数没有被回收,那么回调函数内依赖的变量也没法被回收。 - 下列方法也会存在同样的问题,当不需要使用时,应及时调用对应的方法来取消使用
标题 setInterval clearInterval setTiemout clearTimeout requestAnimationFrame cancelAnimationFrame
(5)遗忘的事件监听器
- 当事件监听器在组件内挂载相关的事件处理函数,而在组件销毁时不主动将其清除时,其中引用的变量或者函数都被认为是需要的而不会进行回收,
- 如果内部引用的变量存储了大量数据,可能会引起页面占用内存过高,这样就造成意外的内存泄漏。