精读 Vue 官方文档系列 🎉
简介
SPA 应用相比与 MPA 更容易产生“内存泄漏”。因为用户在使用 SPA 应用时是不需要刷新浏览器的。此时,就需要应用自身或者是浏览器来清理无用的代码,确保垃圾回收符合预期。
出现的原因
当你在一个 Vue 应用中使用到了非 Vue 的库、插件、组件、事件行为处理、DOM 渲染等,那么就非常有可能产生内存泄漏。
触发的场景
- 组件被销毁时,没有清理相关的依赖。
- 路由进入离开时,没有清理相关的依赖。
- keepAlive 切换时,没有清理相关的依赖。
产生的影响
应用的内存占有不断提高,影响应用的使用性能,带来可能的安全隐患。
实际的例子
下面是一个具体的实例,切换显示/隐藏按钮时,每次都会创建一个新的应用实例。
模板部分:
<button v-if="show" @click="show=false">hide</button>
<button v-if="!show" @click="handleShow">show</button>
<div v-if="show" id="app"></div>
组件逻辑部分:
{
data() {
return {
show: true,
};
},
methods: {
initApp() {
new App({ el: "#app" });
},
handleShow() {
this.show = true;
this.$nextTick(() => {
this.initApp();
});
},
},
mounted() {
this.initApp();
},
}
对于这种场景,就会产生内存泄漏,虽然每次隐藏时 Vue 会清除 #app 里的 DOM 元素,但是构造函数产生的实例在内部的内存引用依然存在,且每次执行 handleShow 方法都会重复创建实例,多次反复,内存的占有就一直提高,并且得不到释放。
解决的方法很简单,那就是定义一个变量来接收每次产生的实例,然后再组件的 beforeDestroy 钩子中执行这个实例的销毁方法。
{
beforeDestroy(){
// this.app = new App({el:'#app'}) in methods handleShow。
this.app.destroyed();
this.app = null;
}
}
举一反三,在 Vue 官方文档的 “示例 - 内嵌组件” 一章中,我们提到了如何在 Vue 中整合基于 Jquery 生态的传统插件或组件。对于类似的场景,必须要引起我们对内存泄漏的注意。
结合上述实例,一些典型的会导致内存泄漏的场景:
- 使用第三方库没有正确调用销毁函数。
- 如果你将一个局部作用域的数据(例如组件)存储到全局中(例如 window),就会产生内存泄漏。典型的例如
EventBus事件总线技术。 - 为当前组件作用域外的 DOM 或对象绑定事件,并且在组件销毁时没有清理,例如为 window 绑定的事件、定时器等。
解决的方案
核心秘诀永远是记得保持警惕,特别是使用了第三方具有 DOM 操作的库。
- 在组件
beforeDestroy钩子中进行清理。 - 对于
keep-alive可以在deactivated中进行清理。
如何检查?
可以借助浏览器的 Memory - Heap snapshot 工具来分析。