【译】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! 🚀🚀

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax