Vue.js 是一个风行且弱小的 JavaScript 框架,它容许咱们构建动静和交互式 Web 应用程序。

然而,与任何软件一样,Vue.js 应用程序有时会遇到内存透露,从而导致性能降落和意外行为。

明天,咱们将深入探讨 Vue.js 应用程序中内存透露的起因,并摸索如何定位和修复这些问题的无效策略。

什么是内存透露 ?

当程序执行过程中保留不再须要的内存时(次要是一些 变量、 办法等),会阻止内存被开释并导致程序的内存使用量随着工夫的推移而增长,称为内存透露。

在 Vue.js 应用程序中,内存透露通常是因为组件、全局 EventBus、事件定时器 和 变量,函数援用的治理不当而引起的。

1. EventBus 引起的内存泄露

一个陈词滥调的话题, Vue.js 中跨组件通信,要么是EventBus, 要么是Vuex 或者Pinia 这种数据流工具。

当咱们对 EventBus 应用不过后,它们可能导致内存透露。
当组件被销毁时,应将它们从事件总线中删除,以避免提早援用。

举个例子:

// 组件A.vue<template>  <div>    <button @click="sendMessage">播送音讯</button>  </div></template><script>import { EventBus } from "./EventBus.js";export default {  methods: {    sendMessage() {      EventBus.$emit("message", "Hello world from A!");    }  }};</script>// 组件B.vue<template>  <div>    <p>{{ receivedMessage }}</p>  </div></template><script>import { EventBus } from "./EventBus.js";export default {  data() {    return {      receivedMessage: ""    };  },  created() {    EventBus.$on("message", message => {      this.receivedMessage = message;    });  }};</script>

在此示例中,产生内存透露是因为 ComponentB 从EventBus订阅了一个事件,但在该组件被销毁时并未勾销订阅。

为了解决这个问题,咱们须要在 组件B 的 beforeDestroy 钩子中应用 EventBus.$off 来删除事件监听器。

所以 须要对组件B做如下批改:

// ComponentB.vue<template>  <div>    <p>{{ receivedMessage }}</p>  </div></template><script>import { EventBus } from "./EventBus.js";export default {  data() {    return {      receivedMessage: ""    };  },  created() {    EventBus.$on("message", message => {      this.receivedMessage = message;    });  },  beforeDestroy() {    EventBus.$off("message"); //this line was missing previously  }};</script>

2. 未被清理的定时器

Vue.js 应用程序中内存透露的最常见起因之一是未能正确删除定时器。当组件在其生命周期中应用了定时器,但并未正当的进行革除。

一旦组件被销毁, 定时器会持续援用该组件,从而避免其被垃圾收集。

举个例子:

<template>   <div>     <button @click="startShow">开始演示</button>     <button @click="stopShow">进行演示</button>   </div> </template> <script> export default {   data() {     return {       intervalId: null     };   },   methods: {     startLeak() {       this.intervalId = setInterval(() => {         // Simulate some activity         console.log("Interval running...");       }, 1000);     },     stopLeak() {       clearInterval(this.intervalId);       this.intervalId = null;     }   } }; </script>

在这个例子中,产生内存透露是因为单击 “开始演示” 按钮时创立了interval 定时,但在组件被销毁时没有正确清理它。

为了解决这个问题,咱们须要在 beforeDestroy 生命周期中,对定时器进行清理。

所以最终的代码将如下所示:

<template>   <div>     <button @click="startShow">开始演示</button>     <button @click="stopShow">进行演示</button>   </div> </template> <script> export default {   data() {     return {       intervalId: null     };   },   methods: {     startLeak() {       this.intervalId = setInterval(() => {         // Simulate some activity         console.log("Interval running...");       }, 1000);     },     stopLeak() {       clearInterval(this.intervalId);       this.intervalId = null;     }   },@diff new     beforeDestroy() {     clearInterval(this.intervalId); // This line is missing above   } }; </script>

3. 第三方库使用不当

第三方类库使用不当,是内存透露的最常见起因。

这是因为组件清理不当造成的。这里我应用 Choices.js 库进行演示。

// cdn Choice Library <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> // our component <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> // Script new Vue({   el: "#app",   data: function () {     return {       showChoices: true     }   },   mounted: function () {     this.initializeChoices()   },   methods: {     initializeChoices: function () {       let list = []       // 发明更多的选项,不便直观的察看到内存透露       for (let i = 0; i < 1000; i++) {         list.push({           label: "选项" + 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     }   } })

在下面的例子中,

咱们加载了一个蕴含许多选项的下拉列表,而后应用带有 v-if 指令的显示/暗藏按钮来增加它并将其从虚构 DOM 中删除。

此示例的问题在于 v-if 指令从 DOM 中删除了父元素,但咱们没有清理 Choices.js 创立的额定 DOM 片段,从而导致内存透露。

我为大家做了一个在线的示例工程,便于大家去察看内存占用状况。

http://demolab.seanz.net/m-leak/index.html


当多点几次 之后,会有明确的内存减少。 附上一张 差别图。

初始化状态:

屡次点击状态:

辨认内存透露

辨认 Vue.js 应用程序中的内存透露可能具备挑战性,因为它们通常体现为性能迟缓或随着工夫的推移内存耗费减少。没有神奇的工具能够辨认代码的问题所在。

然而,大多数古代浏览器都提供内存剖析工具,容许您拍摄应用程序随工夫的内存应用状况的快照。这些工具能够帮忙您辨认哪些对象耗费了过多的内存以及哪些组件没有失去正确的垃圾收集。

Chrome 的 "Heap Snapshot" 等工具能够通过可视化对象援用及其内存耗费来提供对内存应用状况的具体理解。

这能够帮忙您更精确地查明内存透露的本源。

如何尽可能防止内存透露?

  • 正确的应用计时器:确保在 Mounted 生命周期挂钩期间增加 计时器,在组件的 beforeDestroy 钩子中,进行革除。
  • EventBus :当你在组件中应用EventBus时,肯定要确保,在组件销毁钩子办法中 将其 从 EventBus 中删除。
  • 响应式数据清理:在 beforeDestroy 生命周期钩子中,清理响应式数据属性,以避免它们保留对已销毁组件的援用。
  • 第三方类库:当应用在 Vue 之外操作 DOM 的其余 3rd 方库时,通常会产生这些透露。要修复此类透露,请正确遵循库文档并采取适当的措施。

总结

Vue.js 应用程序中的内存透露和性能测试可能很难辨认和解决,而且在疾速交付的兴奋中也很容易被忽视。

然而,放弃较小的内存占用对于整体用户体验依然很重要。

借助正确的工具、技术和实际,您能够显着缩小遇到它们的机会。

通过正确治理一些显著引起内存透露的办法,您能够确保 Vue.js 应用程序以最佳性能运行并放弃衰弱的内存占用。