在前端項目(PC端)中,內存泄露的定位每每比修復更加困難,即便google瀏覽器有提供Memory工具,可是面對成千上萬的元素和錯綜複雜的引用關係,開發則依然很難快速定位到問題代碼塊。前端
1、什麼是內存泄漏?
系統進程再也不用到的內存,沒有及時釋放,就叫作內存泄漏(memory leak)。當內存佔用愈來愈高,輕則影響系統性能,重則致使進程崩潰。Chrome限制了瀏覽器所能使用的內存極限(64位爲1.4GB,32位爲1.0GB),這也就意味着瀏覽器將沒法直接操做一些大內存對象。vue
V8引擎在執行垃圾回收時會阻塞 JavaScript應用邏輯,直到垃圾回收結束再從新執行JavaScript應用邏輯,這種行爲被稱爲「全停頓」(stop-the-world)。 若V8的堆內存爲1.5GB,V8作一次小的垃圾回收須要50ms以上,形成假死現象。node
2、JS內存管理和垃圾回收機制GC
高級語言基本都有垃圾回收機制(garbage collection)自動管理內存,下降程序員的負擔,以達到解決內存泄漏的目的,可是不容許人爲手動觸發,沒法對內存管理進行任何干預。程序員
老版本的瀏覽器使用引用計數法(Reference Counting)來管理內存,即每次引用加一,被釋放時減一,當這個值的引用次數變成 0 時,就能夠將其內存空間回收,缺點是循環引用時沒法回收。vuex
現代瀏覽器基本採用標記清除法(Mark-and-Sweep)來管理內存,即瀏覽器週期性地從某個根元素(譬如 window 對象)開始找引用變量,及這些變量引用的變量,這樣一直找下去。能找到的變量爲可得到變量,不能找到的將被內存回收。
瀏覽器
缺點是清除後內存會產生不少細化的分塊,因此又衍生了標記-整理法,不細講。
閉包
3、VUE中容易出現內存泄露的幾種狀況
內存泄露是一個累積的過程,只有頁面生命週期略長的時候才暴露出問題,頻繁交互可以加快累積的過程,偏展現的頁面很難把這樣的問題暴露出來(所謂刷新一下又能滿血復活)。因此不少時候咱們都是被動式的等待問題暴露而後進行排查的,主動式的分析一般比較難。vue頁面大可能是單頁應用,高交互且停留時間久,處理很差很容易出現內存泄漏。本文章主要針對遊離的dom對象進行排查,普通的JS變量排查有時間再補充。框架
1.全局變量形成的內存泄露dom
<template> <div id="home"> 這裏是首頁 </div> </template> <script> export default { mounted () { window.test = { // 此處在全局window對象中引用了本頁面的dom對象 name: 'home', node: document.getElementById('home') } } } </script>
按下Heap snapshots鍵,搜索Detached,發現沒有脫離文檔樹的dom元素,屬於正常現象
函數
改變路由跳轉到other頁面,按下Heap snapshots鍵,搜索Detached,發現有兩處dom元素遊離於當前頁面以外,很明顯是window對象引用了home頁面中的div,即便此時home頁面已經銷燬,home中的dom元素卻還駐留在內存中沒法釋放。
解決方案就是在頁面卸載的時候順便處理掉該引用。
<template> <div id="home"> 這裏是首頁 </div> </template> <script> export default { mounted () { window.test = { // 此處在全局window對象中引用了本頁面的dom對象 name: 'home', node: document.getElementById('home') } }, destroyed () { window.test = null // 頁面卸載的時候解除引用 } } </script>
2.除了直接引用,window的原生方法也會起到引用dom元素使其沒法釋放的效果。
<template> <div id="home">這裏是首頁</div> </template> <script> export default { mounted () { window.addEventListener('resize', this.func) // window對象引用了home頁面的方法 }, methods: { func () { console.log('這是home頁面的函數') } } } </script>
解決方法同樣,也是在頁面銷燬的時候,順便解除引用,釋放內存
mounted () { window.addEventListener('resize', this.func) }, beforeDestroy () { window.removeEventListener('resize', this.func) }
3.一些全局的方法使用不當也會形成內存沒法釋放,在頁面卸載的時候也能夠考慮解除引用
<template> <div id="home">這裏是首頁</div> </template> <script> export default { mounted () { this.$EventBus.$on('homeTask', res => this.func(res)) }, methods: { func (res) { console.log(res) } } } </script>
mounted () { this.$EventBus.$on('homeTask', res => this.func(res)) }, destroyed () { this.$EventBus.$off() }
形成遊離dom節點的緣由還有不少,不止這三種,總結起來:1.window對象、事件總線、全局vuex上綁定了已銷燬頁面上的節點,到時節點不隨頁面一塊兒銷燬2.使用第三方庫建立實例,第三方庫通常會提供銷燬函數,頁面跳轉時沒有調用正確的銷燬函數3.有同窗會說在頁面中使用閉包也會形成內存泄露,在vue框架裏有管理內存的機制,只要按照它的正確編寫方法,理論上是不會形成內存泄露的