天天看點

es6 javascript 的WeakMap結構

WeakMap結構與Map結構基本類似, 唯一的差別是它隻接受對象作為鍵名( null除外), 不接受其他類型的值作為鍵名, 而且鍵名所指向的對象, 不計入垃圾回收機制。

var map = new WeakMap();
map.set(1, 2);
// TypeError: 1 is not an object!
map.set(Symbol(), 2);
// TypeError: Invalid value used as weak map key
           

上面代碼中, 如果将1和Symbol作為 WeakMap 的鍵名, 都會報錯。

WeakMap的設計目的在于, 鍵名是對象的弱引用( 垃圾回收機制不将該引用考慮在内), 是以其所對應的對象可能會被自動回收。 當對象被回收後, WeakMap自動移除對應的鍵值對。 典型應用是, 一個對應 DOM 元素的WeakMap結構, 當某個 DOM 元素被清除, 其所對應的WeakMap記錄就會自動被移除。 基本上, WeakMap的專用場合就是, 它的鍵所對應的對象, 可能會在将來消失。 WeakMap結構有助于防止記憶體洩漏。

下面是WeakMap結構的一個例子, 可以看到用法上與Map幾乎一樣。

var wm = new WeakMap();
var element = document.querySelector(".element");
wm.set(element, "Original");
wm.get(element) // "Original"
element.parentNode.removeChild(element);
element = null;
wm.get(element) // undefined
           

上面代碼中, 變量wm是一個WeakMap執行個體, 我們将一個DOM節點element作為鍵名, 然後銷毀這個節點, element對應的鍵就自動消失了, 再引用這個鍵名就傳回undefined。

WeakMap 與 Map 在 API 上的差別主要是兩個, 一是沒有周遊操作( 即沒有key()、 values() 和entries() 方法), 也沒有size屬性; 二是無法清空, 即不支援clear方法。 這與WeakMap的鍵不被計入引用、 被垃圾回收機制忽略有關。 是以, WeakMap隻有四個方法可用: get()、 set()、 has()、 delete()。

var wm = new WeakMap();
wm.size
// undefined
wm.forEach
// undefined
前文說過, WeakMap 應用的典型場合就是 DOM 節點作為鍵名。 下面是一個例子。
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {
	timesClicked: 0
});
myElement.addEventListener('click', function() {
	let logoData = myWeakmap.get(myElement);
	logoData.timesClicked++;
	myWeakmap.set(myElement, logoData);
}, false);
           

上面代碼中, myElement是一個 DOM 節點, 每當發生 click 事件, 就更新一下狀态。 我們将這個狀态作為鍵值放在 WeakMap 裡, 對應的鍵名就是myElement。 一旦這個 DOM 節點删除, 該狀态就會自動消失, 不存在記憶體洩漏風險。

WeakMap 的另一個用處是部署私有屬性。

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
	constructor(counter, action) {
		_counter.set(this, counter);
		_action.set(this, action);
	}
	dec() {
		let counter = _counter.get(this);
		if(counter < 1) return;
		counter--;
		_counter.set(this, counter);
		if(counter === 0) {
			_action.get(this)();
		}
	}
}
let c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
           

上面代碼中, Countdown 類的兩個内部屬性_counter和_action, 是執行個體的弱引用, 是以如果删除執行個體, 它們也就随之消失, 不會造成記憶體洩漏。

繼續閱讀