Close
升级到 Vue 3 | Vue 2 EOL

避免内存泄漏

介绍

如果您正在使用 Vue 开发应用程序,那么您需要注意内存泄漏。这个问题在单页应用程序 (SPA) 中尤为重要,因为根据设计,用户在使用 SPA 时不应该刷新浏览器,因此 JavaScript 应用程序需要清理组件并确保垃圾回收按预期进行。

Vue 应用程序中的内存泄漏通常不是来自 Vue 本身,而是可能发生在将其他库集成到应用程序时。

简单示例

以下示例展示了使用 Choices.js 库在 Vue 组件中并未正确清理而导致的内存泄漏。稍后,我们将展示如何删除 Choices.js 的影响并避免内存泄漏。

在下面的示例中,我们使用许多选项加载一个选择框,然后使用带有 v-if 指令的显示/隐藏按钮将其添加到虚拟 DOM 并从中移除。此示例的问题在于,v-if 指令从 DOM 中删除了父元素,但我们没有清理 Choices.js 创建的额外 DOM 部分,从而导致内存泄漏。

<link rel='stylesheet prefetch' href='https://joshuajohnson.co.uk/Choices/assets/styles/css/choices.min.css?version=3.0.3'>
<script src='https://joshuajohnson.co.uk/Choices/assets/scripts/dist/choices.min.js?version=3.0.3'></script>

<div id="app">
<button
v-if="showChoices"
@click="hide"
>Hide</button>
<button
v-if="!showChoices"
@click="show"
>Show</button>
<div v-if="showChoices">
<select id="choices-single-default"></select>
</div>
</div>
new Vue({
el: "#app",
data: function () {
return {
showChoices: true
}
},
mounted: function () {
this.initializeChoices()
},
methods: {
initializeChoices: function () {
let list = []
// lets load up our select with many choices
// so it will use a lot of memory
for (let i = 0; i < 1000; i++) {
list.push({
label: "Item " + i,
value: i
})
}
new Choices("#choices-single-default", {
searchEnabled: true,
removeItemButton: true,
choices: list
})
},
show: function () {
this.showChoices = true
this.$nextTick(() => {
this.initializeChoices()
})
},
hide: function () {
this.showChoices = false
}
}
})

要查看此内存泄漏的实际情况,请使用 Chrome 打开此 CodePen 示例,然后打开 Chrome 任务管理器。要在 Mac 上打开 Chrome 任务管理器,请选择 Chrome 顶部导航 > 窗口 > 任务管理器,或在 Windows 上使用 Shift+Esc 快捷键。现在,单击显示/隐藏按钮 50 次左右。您应该会看到 Chrome 任务管理器中的内存使用量增加,并且永远不会被回收。

Memory Leak Example

解决内存泄漏

在上面的示例中,我们可以在将选择框从 DOM 中删除之前使用我们的 hide() 方法进行一些清理并解决内存泄漏。为此,我们将在 Vue 实例的 data 对象中保留一个属性,并将使用 Choices APIdestroy() 方法执行清理。

使用此 更新的 CodePen 示例 再次检查内存使用量。

new Vue({
el: "#app",
data: function () {
return {
showChoices: true,
choicesSelect: null
}
},
mounted: function () {
this.initializeChoices()
},
methods: {
initializeChoices: function () {
let list = []
for (let i = 0; i < 1000; i++) {
list.push({
label: "Item " + i,
value: i
})
}
// Set a reference to our choicesSelect in our Vue instance's data object
this.choicesSelect = new Choices("#choices-single-default", {
searchEnabled: true,
removeItemButton: true,
choices: list
})
},
show: function () {
this.showChoices = true
this.$nextTick(() => {
this.initializeChoices()
})
},
hide: function () {
// now we can use the reference to Choices to perform clean up here
// prior to removing the elements from the DOM
this.choicesSelect.destroy()
this.showChoices = false
}
}
})

关于值的详细信息

在快速发布的兴奋中,内存管理和性能测试很容易被忽视,但是,保持较小的内存占用量对于整体用户体验仍然很重要。

考虑您的用户可能使用的设备类型以及他们的正常流程。他们可能会使用内存受限的笔记本电脑或移动设备吗?您的用户通常会进行大量的应用程序内导航吗?如果以上两者都属实,那么良好的内存管理实践可以帮助您避免用户浏览器崩溃的最坏情况。即使以上两者都不属实,如果您不小心,在长时间使用应用程序时,您仍然可能会出现性能下降。

现实世界示例

在上面的示例中,我们使用 v-if 指令来说明内存泄漏,但更常见的现实世界场景是在使用 vue-router 在单页应用程序中路由到组件时发生的。

就像 v-if 指令一样,vue-router 在用户在应用程序中导航时会从虚拟 DOM 中删除元素并用新元素替换它们。Vue 的 beforeDestroy() 生命周期钩子 是在基于 vue-router 的应用程序中解决相同问题的理想位置。

我们可以将清理操作移到 beforeDestroy() 钩子中,如下所示

beforeDestroy: function () {
this.choicesSelect.destroy()
}

替代模式

我们已经讨论了在删除元素时管理内存,但是如果您有意要保留状态并将元素保存在内存中怎么办?在这种情况下,您可以使用内置的组件 keep-alive

当您用 keep-alive 包裹组件时,它的状态将被保留,因此会保存在内存中。

<button @click="show = false">Hide</button>
<keep-alive>
<!-- my-component will be intentionally kept in memory even when removed -->
<my-component v-if="show"></my-component>
</keep-alive>

此技术可以用来改善用户体验。例如,想象一下,用户开始在文本输入框中输入评论,然后决定导航离开。如果用户随后导航回来,他们的评论将仍然保留。

一旦您使用 keep-alive,您就可以访问另外两个生命周期钩子:activateddeactivated。如果您确实想要在删除 keep-alive 组件时清理或更改数据,您可以在 deactivated 钩子中执行此操作。

deactivated: function () {
// remove any data you do not want to keep alive
}

总结

Vue 使得开发出色的、响应式的 JavaScript 应用程序变得非常容易,但您仍然需要小心内存泄漏。这些泄漏通常发生在使用额外第三方库在 Vue 之外操作 DOM 时。确保测试您的应用程序是否存在内存泄漏,并在必要时采取适当的步骤清理组件。