前言
上一篇我们简单聊了下js中的原始值与引用值,以及传递参数 。
本篇文章我们看一看js的垃圾回收机制,以及在实际开发中,可能出现的内存泄漏的情况,以及克制内存泄漏手段
JS垃圾回收主要策略
1.标记清理
标记清理是js最常用的垃圾回收策略
当垃圾回收程序运行的时候,会标记内存中存储的所有变量(标记方法不唯一),之后他会将所有在上下文的变量,以及被在上下文中的变量引用的标记都去掉。
关于上下文补充说明:
上下文分为全局上下文和函数上下文(eval()中存在第三种上下文), 执行上下文中了包含变量环境、词法环境、this指向、作用域链等
运行代码时,会开辟多块执行上下文,生成词法环境,变量被暂时压入词法环境的栈结构,用于执行。
当然,在生成变量环境时,var声明的变量都会被提升到当前作用域的最前端
在代码执行时,程序沿着作用域链条,从内到外,从上到下的搜索变量与函数
想详细了解上下文的同学,可以看看这篇大神文章:blog.csdn.net/Lin__hr/art…
在此之后再被加上标记的变量就是应该带删除的变量,因为任何上下文中的变量都无法访问它们。
随后垃圾回收程序做一次内存清理(不同浏览器清理频率有所不同)
2.引用计数
引用计数是js中不算常用的垃圾回收策略,主要设计思路为:对每个值都记录它被引用的次数,声明变量并且给它赋一个引用值时,这个值的引用数变为1,如果该值又被赋给另外一个变量 那么这个值引用次数加一
同理,每当指向该引用的变量失效时,该值引用数就减一
当一个值引用数变为0时,垃圾回收程序下次运行就会释放引用数为0的变量内存
- 引用计数策略缺点:
要命的是,引用计数有一个致命的缺陷,无法回收循环引用的对象
我们直接上代码加深印象:
function problem() {
let ObjectA = new Object()
let ObjectB = new Object()
ObjectA.someOtherObject = ObjectB
ObjectB.anotherObject = ObjectA
}
在这段代码中,由于两个对象始终相互引用,引用计数永不为零,也就永不被回收
关于引用计数的优缺点,可以看看这篇大神的文章:juejin.cn/post/702836…
3.JS内存泄漏场景有哪些?(以VUE为例),以及如何解决内存泄漏
3.1被全局变量、函数引用,组件销毁时未清除
export default {
name: 'Memory Leak Demo',
data() {
return {
arr: [10, 20, 30], // 数组 对象
}
},
methods: {
mounted()
},
mounted() {
// 变量挂载在全局对象上
window.arr = this.arr
// 变量被全局函数使用
window.printArr = () => {
console.log(this.arr)
}
// 清除方法,释放内存
beforeUnmount() {
window.arr = null
window.printArr = null
},
}
}
清除手段: 我们可以在vue实例销毁之前,于钩子函数beforeUnmount()手动释放全局变量和全局函数
3.2,定时器引用,组件销毁时未清除
export default {
name: 'Memory Leak Demo',
data() {
return {
arr: [10, 20, 30], // 数组 对象
intervalId:0
}
},
methods: {
printArr() {
console.log(this.arr)
}
},
mounted() {
// 定时器引用变量
this.intervalId = setInterval(() => {
console.log(this.arr)
}, 100)
},
// Vue2 - beforeDestroy
beforeUnmount() {
if(this.intervalId) {
clearInterval(this.intervalId)
}
}
}
清除手段:
我们可以在vue实例销毁之前,于钩子函数beforeUnmount()中移除掉这个定时器
3.3被全局事件引用,组件销毁时未清除
export default {
name: 'Memory Leak Demo',
data() {
return {
arr: [10, 20, 30], // 数组 对象
}
},
methods: {
printArr() {
console.log(this.arr)
}
},
mounted() {
window.addEventListener('resize', this.printArr)
// 全局事件也是这样
},
// Vue2 - beforeDestroy
beforeUnmount() {
window.removeEventListener('resize', this.printArr)
},
}
清除手段:
我们可以在vue实例销毁之前,于钩子函数beforeUnmount()中移除掉这个全局事件
4.小结
-
标记清除是主流的js垃圾回收策略,原理为:给当前不使用的变量加上标记,再回来回收它们的内存
-
变量的执行上下文用于确定什么时候释放内存
-
引用计数是非主流的js垃圾回收策略,需要记录值被引用次数,JavaScript引擎不再使用这种策略,但是有时候JavaScript需要访问非原生js对象,这些对象有可能使用的是该策略,因此要小心代码中的循环引用情况,可以通过设置null值,来解除引用