【译】vue.js 内存泄漏的识别和解决方案

vue.js 内存泄漏的识别和解决方案

免责声明

渣翻译,仅供大家参考

原文链接:blog.jobins.jp/vuejs-memor...

毋庸置疑, .Vue.js是非常流行和强大的Javascript框架, 用来让我们构建动态和交互式的WEB应用,然而, 如同很多软件,Vue.js应用有时候会出现内存溢出从而导致卡顿和一些异常的行为,今天,我们将深入Vue.js应用内存泄漏的原因,并找出有效的方法来识别和修复这些问题

什么是内存泄漏

当一个程序已经很长时间不需要但是仍然意外的保持了内存,阻止了内存释放并且随着时间流逝内存持续增长的原因, 称之为内存泄漏, 在Vue.js应用中,比较典型的内存溢出出现在不正确的组件管理,全局event bus, 时间监听和传参

让我们通过一些例子来演示Vue.js应用的内存泄漏, 并修复它们

1.全局Event Bus泄漏

全局Event Bus用于组件之间的通信,如果不正确的管理则会导致内存泄漏,如果组件销毁,应该删除event bus从而阻止传参遗留

例如:

javascript 复制代码
// EventBus.js
import Vue from "vue";
export const EventBus = new Vue();

// ComponentA.vue
<template>
  <div>
    <button @click="sendMessage">Send Message</button>
  </div>
</template>

<script>
import { EventBus } from "./EventBus.js";
export default {
  methods: {
    sendMessage() {
      EventBus.$emit("message", "Hello from Component A!");
    }
  }
};
</script>

// 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;
    });
  }
};
</script>

在这个例子中, 由于组件B从全局event bus订阅了一个事件, 但是在销毁的时候没有进行取消订阅, 所以出现了意外的内存泄漏, 修复这个问题, 我们需要在组件B的beforeDestroy声明周期中, 用EventBus.$off来删除事件订阅,所以组件B看起来如下:

javascript 复制代码
// 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应用内存泄漏常见原因是没有删除事件监听, 当一个组件在他的生命周期中只注册了事件监听, 但没有移除他们, 当组件卸载, 监听器仍然继续引用组件, 从而导致阻止了事件的垃圾回收

例如:

javascript 复制代码
 <template>
   <div>
     <button @click="startLeak">Start Memory Leak</button>
     <button @click="stopLeak">Stop Memory Leak</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>

这里, 因为点击Start Memory Leak创建了事件监听(interval),但是组件销毁的时候没有完全的删除, 所以出现了意外的内存泄漏, 要修正他, 我们需要在beforeDestroy钩子清除interval

最后的代码如下

javascript 复制代码
 <template>
   <div>
     <button @click="startLeak">Start Memory Leak</button>
     <button @click="stopLeak">Stop Memory Leak</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;
     }
   },
   beforeDestroy() {
     clearInterval(this.intervalId); // This line is missing above
   }
 };
 </script>

3.第三方库

内存泄漏常见原因,还出现在组件没有正确的清理,这里我用了Choices.js库来演示

javascript 复制代码
 // 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 = []
       // loading many option to increate memory usage
       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
     }
   }
 })

在这个例子上, 我们加载了一个有大量选项的select下拉框, 并且通过Show/Hide按钮结合

v-if指令来添加删除他们的虚拟dom,这个的问题在于v-if指令从DOM中删除了父级组件, 但是我们没有清理Choices.js创建的附加DOM, 所以出现了内存泄漏


在Chrome浏览器中打开项目,到谷歌任务管理器,观察这个组件的内存使用情况,当你点击

Show/Hid按钮,每次点击当前页面的内存占用都会增长,当你停止点击, 也没有释放占用的内存

javascript 复制代码
new Vue({
  el: "#app",
  data: function () {
    return {
      showChoices: true,
      choicesSelect: null // creates a variable to for reference
    }
  },
  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
      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  clean up reference
      this.choicesSelect.destroy()
      this.showChoices = false
    }
  }
})

这里有一个demo中谷歌任务管理的截图

点击Show/Hide按钮之前

点击50, 60次显示/隐藏两个tabs

获取这个问题以及解决办法的详细demo请访问我的GitHub Repo,clone项目, 并且在谷歌浏览器打开index.html你就可以运行

确认内存溢出

确认vue.js应用中的内存溢出是富有挑战的, 因为他们经常表现为卡顿或者占用内存越来越高,没有什么万能的工具能确认你的代码哪里出现了问题

然而,大多数现代浏览器提供了内存分析工具, 允许你创建应用内存使用的快照,这些工具可以帮助你确认哪些对象占用了额外的内存,哪些组件没有正确的垃圾回收

例如谷歌的Heap Snapshot(堆快照)就通过可视化对象应用和内存消耗来提供内存使用的详细分析,这些可以帮你准确的定位内存溢出的原因

修正vue.js应用中的内存溢出

1.正确的时间监听管理: 保证事件监听器在组件的mounted生命周期中添加, 在beforeDestroy生命周期中删除

2.解决循环引用的问题: 如果在组件之间创建循环引用一定要谨慎,如果这些循环引用是必要的,一定要确保组件销毁后终止引用

3.清除全局Event Bus:组件销毁时,在合适的生命周期钩子中删除全局Event Bus

4.清除响应式数据:使用beforeDestroy生命周期钩子清除响应式数据属性, 以阻止组件已经销毁了,但是还引用这些属性

5.第三方依赖库:有些三方库在vue之外操作dom经常出现意外泄漏, 要修复这种类型的泄露,需要根据文档指引采取恰当的措施

结论

在快速交付的vue.js应用中, 内存溢出和性能测试是比较难以发现和容易忽视的

然而,保持最小的内存占用对于整体用户体验是非常重要的

用正确的工具,正确的技术方案,正确的开发习惯, 你可以显著的减少遇到他们的可能性.正确的管理事件监听,循环引用,全局event buses, 和响应式数据,你可以保证你的vue.js应用稳定运行并保持健康的内存占用

Bonus:

为什么计算机需要治疗?

因为他有很多内存问题要解决!😄


Happy Coding! 🚀🚀

相关推荐
Easonmax6 分钟前
【CSS3】css开篇基础(1)
前端·css
大鱼前端24 分钟前
未来前端发展方向:深度探索与技术前瞻
前端
昨天;明天。今天。29 分钟前
案例-博客页面简单实现
前端·javascript·css
天上掉下来个程小白31 分钟前
请求响应-08.响应-案例
java·服务器·前端·springboot
周太密1 小时前
使用 Vue 3 和 Element Plus 构建动态酒店日历组件
前端
时清云1 小时前
【算法】合并两个有序链表
前端·算法·面试
小爱丨同学2 小时前
宏队列和微队列
前端·javascript
持久的棒棒君2 小时前
ElementUI 2.x 输入框回车后在调用接口进行远程搜索功能
前端·javascript·elementui
2401_857297912 小时前
秋招内推2025-招联金融
java·前端·算法·金融·求职招聘
undefined&&懒洋洋3 小时前
Web和UE5像素流送、通信教程
前端·ue5