一、ES6 WeakMap
參考
JavaScript 內(nèi)存泄漏教程
ES6詳解四: WeakMap
及時清除引用非常重要。但是,你不可能記得那么多刽脖,有時候一疏忽就忘了,所以才有那么多內(nèi)存泄漏忌愚。最好能有一種方法曲管,在新建引用的時候就聲明,哪些引用必須手動清除硕糊,哪些引用可以忽略不計院水,當其他引用消失以后,垃圾回收機制就可以釋放內(nèi)存简十。這樣就能大大減輕程序員的負擔檬某,你只要清除主要引用就可以了。
ES6 考慮到了這一點螟蝙,推出了兩種新的數(shù)據(jù)結(jié)構(gòu):WeakSet 和WeakMap恢恼。它們對于值的引用都是不計入垃圾回收機制的,所以名字里面才會有一個"Weak"胰默,表示這是弱引用场斑。(Map的一個最大弊端就是它會導(dǎo)致作為key的對象增加一個引用,因此導(dǎo)致GC無法回收這個對象初坠,如果大量使用object作為Map的key會導(dǎo)致大量的內(nèi)存泄露和簸。)
下面代碼中,只要外部的引用消失碟刺,WeakMap 內(nèi)部的引用锁保,就會自動被垃圾回收清除。由此可見,有了它的幫助爽柒,解決內(nèi)存泄漏就會簡單很多吴菠。
// 手動執(zhí)行一次垃圾回收,保證獲取的內(nèi)存使用狀態(tài)準確
> global.gc();
undefined
// 查看內(nèi)存占用的初始狀態(tài)浩村,heapUsed 為 4M 左右
> process.memoryUsage();
{ rss: 21106688,
heapTotal: 7376896,
heapUsed: 4153936,
external: 9059 }
> let wm = new WeakMap();
undefined
> let b = new Object();
undefined
> global.gc();
undefined
// 此時做葵,heapUsed 仍然為 4M 左右
> process.memoryUsage();
{ rss: 20537344,
heapTotal: 9474048,
heapUsed: 3967272,
external: 8993 }
// 在 WeakMap 中添加一個鍵值對,
// 鍵名為對象 b心墅,鍵值為一個 5*1024*1024 的數(shù)組
> wm.set(b, new Array(5*1024*1024));
WeakMap {}
// 手動執(zhí)行一次垃圾回收
> global.gc();
undefined
// 此時酿矢,heapUsed 為 45M 左右
> process.memoryUsage();
{ rss: 62652416,
heapTotal: 51437568,
heapUsed: 45911664,
external: 8951 }
// 解除對象 b 的引用
> b = null;
null
// 再次執(zhí)行垃圾回收
> global.gc();
undefined
// 解除 b 的引用以后,heapUsed 變回 4M 左右
// 說明 WeakMap 中的那個長度為 5*1024*1024 的數(shù)組被銷毀了
> process.memoryUsage();
{ rss: 20639744,
heapTotal: 8425472,
heapUsed: 3979792,
external: 8956 }
二怎燥、Laya中緩存的需求
以Button為例瘫筐,設(shè)置的skin圖片很多是有UP,OVER,DOWN三態(tài)的,也就是在顯示時铐姚,要根據(jù)stateNum去切割Texture策肝。那么一個按鈕在UP,OVER,DOWN狀態(tài)切換時,是不可能在切換時才做切割的隐绵,一定是初始化skin時就切割好了之众。參考Button.as
public function set skin(value:String):void {
if (_skin != value) {
_skin = value;
callLater(changeClips);
_setStateChanged();
}
}
/**
* @private
* 對象的資源切片發(fā)生改變。
*/
protected function changeClips():void {
var img:Texture = Loader.getRes(_skin);
if (!img) {
trace("lose skin", _skin);
return;
}
var width:Number = img.sourceWidth;
var height:Number = img.sourceHeight / _stateNum;
img.$_GID || (img.$_GID = Utils.getGID());
var key:String = img.$_GID +"-"+ _stateNum;
var clips:Array = WeakObject.I.get(key);
if (clips) _sources = clips;
else {
_sources = [];
if (_stateNum === 1) {
_sources.push(img);
} else {
for (var i:int = 0; i < _stateNum; i++) {
_sources.push(Texture.createFromTexture(img, 0, height * i, width, height));
}
}
WeakObject.I.set(key, _sources);
}
...
/**
* @private
* 改變對象的狀態(tài)依许。
*/
protected function changeState():void {
_stateChanged = false;
runCallLater(changeClips);
var index:int = _state < _stateNum ? _state : _stateNum - 1;
_sources && (_bitmap.source = _sources[index]);
...
/**@inheritDoc */
override public function destroy(destroyChild:Boolean = true):void {
super.destroy(destroyChild);
_bitmap && _bitmap.destroy();
_text && _text.destroy(destroyChild);
_bitmap = null;
_text = null;
_clickHandler = null;
_labelColors = _sources = _strokeColors = null;
}
在changeClips方法中棺禾,可以看到使用WeakObject緩存了這些切割后的Texture。那么問題就是峭跳,_sources已經(jīng)在Button中對切片做了緩存帘睦,為什么還要緩存到另外一個類WeakObject中呢?
我覺得原因是這樣的坦康,如果一個或多個UI中出現(xiàn)了許多個同樣的Button,那么_sources在每一個Button實例中做緩存是不夠的诡延,肯定是要統(tǒng)一緩存到一個地方滞欠,那就是WeakObject了。然后在每一個Button實例中肆良,在destory方法中都把_sources置為null清除了對切片的引用筛璧,如果所有的Button實例都destory了,那么WeakObject中的切片緩存就會自動被GC惹恃。而普通的Map或者說Object是做不到這一點的夭谤,它無法知道自己這個緩存還有沒有Button在使用。從WeakObject.as中可以看出這一點:
public static function __init__():void {
supportWeakMap = Browser.window.WeakMap != null;
//如果不支持巫糙,10分鐘回收一次
if (!supportWeakMap) Laya.timer.loop(delInterval, null, clearCache);
}
/**清理緩存朗儒,回收內(nèi)存*/
public static function clearCache():void {
for (var i:int = 0, n:int = _maps.length; i < n; i++) {
var obj:WeakObject = _maps[i];
obj._obj = {};
}
}
三、應(yīng)用場景
官方引擎中,除了Button醉锄,Clip和AutoBitmap也使用了WeakObject乏悄。可以看出恳不,當多個UI需要緩存一些共用的對象時檩小,并且只想各自destroy,不想管緩存的內(nèi)存回收問題時烟勋,就可以使用WeakObject了规求。這個和Pool是不同的,Pool是對象池卵惦,主要是減少多余的對象創(chuàng)建阻肿,池子取空后,仍然是要new出新實例來返回鸵荠。而WeakObject緩存的只有一份數(shù)據(jù)冕茅,只要key相同,所有實例用的都是那個緩存蛹找。