Vue依賴收集引發的問題
問題背景
在我們的專案中有一個視覺化配置的模組,是通過go.js生成canvas來實現的。但是,我們發現這個模組在瀏覽器中經常會引起該tab頁崩潰。開啟chrome的工作管理員一看,進入該頁面記憶體和cpu就會暴漲,記憶體經常會飆到700多M。但是我們的canvas實際的畫素只有約500x500,根據一些粗略的計算,大概只佔了1M的記憶體,這個計算過程可參考 100*100的 canvas 佔多少記憶體 。那麼我們這700M記憶體是哪裡來的呢?
定位問題
我們可以使用chrome開發者工具來分析我們的呼叫棧。這邊我是先通過Performance來幫助我們定位問題,它會幫我們生成一段過程中一些資料的變化,包括js堆記憶體、dom節點數量、動畫幀等資料,如圖:
這是切換至一個canvas畫布較大的一個模組的performance分析表現,可以看到佔用了472M的記憶體。下面折線圖藍色部分是js堆記憶體的變化,而Main下面黃色與紫色的矩形框就是我們的呼叫棧,上下兩部分是按照時間一一對應的。可以看到,藍色的折線呈高低起伏的態勢,GC回收之後低點基本和高點持平,因此可以斷定幾乎不存在記憶體洩漏的問題。然後我們可以放大去看一看,記憶體升高的時候,js做了些什麼事情,找一找規律。
我們隨機找一段記憶體增長的區域,可以看到在記憶體增長的過程中,最為頻繁呼叫的就是Observer相關的程式碼。但是就這麼看,我們不能夠明白Observer是在幹什麼。此時我們可以借用Memory選項中的Allocation Sampling按照javascript function來檢視記憶體分配,我們同樣錄製以上的一段操作。
此時我們能夠清楚的看到,的確是這個Observer在作怪。同時,我們可以看到這是vue的程式碼,點選右邊的檔案檢視source code,就可以清楚的明白這就是vue在執行依賴收集的操作,此時會給屬性新增watcher。那我們這裡為什麼會有如此多的屬性被添加了watcher呢?看了一下程式碼,原來是我把go.js的一個例項掛到了vue的data選項中,放到data中的屬性會被vue執行依賴收集的相關操作,而這個例項擁有非常多的巢狀屬性,全部都會被新增watcher。其實,我們只是想單純的儲存一下這個例項,供我們後續呼叫其相關的方法,新增watcher對我們來說完全沒有意義,那我們如何避免這樣的問題呢?
解決問題
上網搜尋了相關的解決方案,大概有如下幾種:
javascript data() { return { $goDiagram: null } }
export default { goDiagram: null, mounted() { this.$options.goDiagram = xxx } }
這應該是比較好的一個方法,vue官方中也說明了$options用來包含自定義屬性,例如我們平時引入的常量或是列舉型別,我們也不希望它們被新增無意義的watcher,因此可以通過這種方式來定義,在template中引用時只需要{{$options.xxx}}即可。這種方式唯一的缺點就是不能像data那樣一眼望去就能清楚地知道你定義了什麼屬性。
專案中我採用了第一種方式,經過修改後記憶體佔用量減少到原來的1/5到1/6,可以說效果非常好,再也不會出現瀏覽器崩潰的情況了。
總結
通過這樣的一個問題,我們主要能夠學習到兩點:
- 如何通過chrome的開發者工具,去快速地定位程式碼中存在的記憶體問題
- 不要盲目的將屬性都掛載到data選項中,一些常量我們可以採取上面提到的幾種方式來定義,以此來作為一種優化手段