關(guān)于性能:
首先我不得不承認(rèn)一個事實(shí),移動端的性能跟PC端,那完全不是一回事
比如用innerHTML繪制大段的HTML結(jié)構(gòu),之后同步獲取生成HTML中的ID節(jié)點(diǎn),結(jié)果不存在
這種問題在單頁面模擬多頁面,動態(tài)創(chuàng)建DOM的時候,尤為明顯
var element = $('<div id = "aaron">...填充大量結(jié)構(gòu)...</div>'); $(root).html(element) $('#aaron') //為空
關(guān)于JavaScript內(nèi)存管理:
原文:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management
JavaScript會給開發(fā)者一個錯覺:可以不用考慮內(nèi)存管理
現(xiàn)代瀏覽器已經(jīng)夠聰明了,從2012年起,所有現(xiàn)代瀏覽器都使用了標(biāo)記-清除垃圾回收算法。所有對JavaScript垃圾回收算法的改進(jìn)都是基于標(biāo)記-清除算法的改進(jìn),并沒有改進(jìn)標(biāo)記-清除算法本身和它對“對象是否不再需要”的簡化定義。
所以引用計數(shù)收集與循環(huán)引用之類的都不再是問題了,過去導(dǎo)致內(nèi)存泄漏的許多經(jīng)典模式在現(xiàn)代瀏覽器中以不再導(dǎo)致泄漏內(nèi)存。
但是,如今有一種不同的趨勢影響著內(nèi)存泄漏。許多人正設(shè)計用于在沒有硬頁面刷新的單頁中運(yùn)行的 Web 應(yīng)用程序。在那樣的單頁中,從應(yīng)用程序的一個狀態(tài)到另一個狀態(tài)時,很容易保留不再需要或不相關(guān)的內(nèi)存。
典型的就是: 單頁面模擬多頁面的行為
簡單的內(nèi)存管理測試
視圖:
HTML結(jié)構(gòu):
<button id="start_button">Start</button> <button id="destroy_button">Destroy</button>
腳本代碼:
var Leaker = function() { this.name = 'aaron' }; $("#start_button").click(function(){ leak = new Leaker(); }); $("#destroy_button").click(function(){ leak = null; });
點(diǎn)擊Start 產(chǎn)生一個對象leak = new Leaker();
點(diǎn)擊Destroy 銷毀這個對象 觀察下內(nèi)存中變化(工具后面會提到)
點(diǎn)擊Start ,產(chǎn)生一個對象
點(diǎn)擊Destroy,對象銷毀
那么這個圖很形象的說明了,#Delata釋放了一個實(shí)例,就是內(nèi)存被回收了
如果不做任何處理,那么這個對象leak始終最存在整個生命周期內(nèi)(全局上下文的情況)
如果leak = null,內(nèi)存確實(shí)是由瀏覽器GC 自動給回收了
閉包引起的內(nèi)存泄漏:
代碼:內(nèi)部增加了一個定時器,遞歸調(diào)用
var count = 0; var Leaker = function(){}; Leaker.prototype = { init:function(){ this._interval = null; this.start(); }, start: function(){ var self = this; //遞歸調(diào)用自身 this._interval = setInterval(function(){ self.onInterval(); }, 100); }, destroy: function(){ if(this._interval !== null){ clearInterval(this._interval); } }, onInterval: function(){ count++; console.log("Interval",count); } };
從樣的觀察
我在按了銷毀,leak = null了
可見代碼依然還在走,可見此時內(nèi)存絕對的溢出了,也就是失控了
但是監(jiān)視器顯示該對象回收了
那么這個問題就很明顯了,通過leak = null 銷毀的只是引用,內(nèi)部如果還存在引用的話,這個heap是不會被回收的
此時這個內(nèi)存我們已經(jīng)管理不到了,會一直遞歸下去
要解決只能在銷毀的時候先停止定時器了
由此可見,引用不僅僅只是外部的, 內(nèi)部同樣存在這樣的問題,當(dāng)然引用類型的機(jī)制本來就是這樣的
所以在日常的代碼編寫方面,JS的坑確實(shí)不少,接下來看看我項(xiàng)目中的大坑吧!??!
應(yīng)用截圖:
內(nèi)存使用檢測:
Eclipse
Eclipse不熟悉的路過,我們還是回到前端的角度去處理
使用Chrome DevTools的Timeline和Profiles提高Web應(yīng)用程序的性能
具體的使用就不介紹了,大家接著看
抓怕的heap快照,實(shí)時反饋的信息
系統(tǒng)的閉包數(shù)
加上JQuery
項(xiàng)目中的
視圖解釋
列字段解釋:
Constructor -- 構(gòu)造器
Distance -- 估計是對象到根的引用層級距離
Objects Count -- 給出了當(dāng)前有多少個該類的對象
Shallow Size -- 對象所占內(nèi)存(不包含內(nèi)部引用的其它對象所占的內(nèi)存)(單位:字節(jié))
Retained Size -- 對象所占總內(nèi)存(包含內(nèi)部引用的其它對象所占的內(nèi)存)(單位:字節(jié))
小伙伴都嚇呆了
項(xiàng)目中除去系統(tǒng)與一些插件的,至少有上千個閉包
Object's retaining tree視圖顯示出了該對象被哪些對象引用了,以及這個引用的名稱
關(guān)于XUTUTIL.Event類
XUTUTIL.Event是一個構(gòu)函數(shù)函數(shù),主要就是一個訂閱/發(fā)布模式
那么這個圖我的理解就是通過XUTUTIL構(gòu)造生成的的對象都應(yīng)該是放到這個里面,所以
根據(jù)分析圖顯示,這個類有208個對象,被實(shí)例了208次,也就是說存在這么多訂閱者了
XUTUTIL部分源碼(觀察者模式)
XUTUTIL.Event
如圖me.events[eventName]標(biāo)記,是數(shù)組保存了觀察對象了
點(diǎn)擊圖中的黑色實(shí)心圓圈按鈕,即可得到第二個內(nèi)存快照:
點(diǎn)擊圖中的“Summary”,可彈出一個列表,選擇“Comparison”選項(xiàng),然后選擇對比第一個,結(jié)果如下圖:
這個視圖列出了當(dāng)前視圖與上一個視圖的對象差異。
列名字段解釋:
# New -- 新建了多少個對象
# Deleted -- 回收了多少個對象
# Delta -- 對象變化值,即新建的對象個數(shù)減去回收了的對象個數(shù)
ALLOC -- 變化的內(nèi)存大小(字節(jié))注意Delta字段,尤其是值大于0的對象
很明顯翻一頁就創(chuàng)建大量的觀察對象
*注:因?yàn)槭菃雾撁鎽?yīng)用,動態(tài)多頁面的翻頁算法,比如當(dāng)前是從第2頁到第3頁,其實(shí)是預(yù)先創(chuàng)建第4頁面,銷毀第1頁,保留234頁,所以這個+14,不是這樣算的
但是第一個很明顯的問題就出來,為什么要動態(tài)創(chuàng)建這么多的觀察對象,找到代碼來源
找到問題了
注冊了大量的觀察者模式
銷毀的代碼,沒有處理注銷觀察者事件
啪啪啪啪。。。。一陣修改之后
翻頁的時候不處理了
在進(jìn)入頁面初始化的時候208變成18個了。。。在看看內(nèi)存占用。。。45016---3240
PC上的消耗,在移動端就會被放大的,所以不要放過過任何一個可優(yōu)化的地方
修改前
修改后
因?yàn)檫@個案例比較明顯,還有的問題,要靠自己慢慢去分析引用情況了
那么很明顯了:觀察者模式引起的內(nèi)存泄漏
需要觀察者模式(Observer)來解藕一些模塊,但如果使用不當(dāng),也會帶來內(nèi)存泄漏的問題。
排查這類型的內(nèi)存泄漏問題,主要重點(diǎn)關(guān)注被引用的對象類型是閉包(closure)和數(shù)組Array的對象。
1.如果能避免觀察模式的使用,就盡量避免,
2.避免不了一定要記得清理
總結(jié)出以下幾種常見的情況:
1.閉包上下文綁定后沒有釋放;
2.觀察者模式在添加通知后,沒有及時清理掉;
3.定時器的處理函數(shù)沒有及時釋放,沒有調(diào)用clearInterval方法;
4.視圖層有些控件重復(fù)添加,沒有移除。