ES6 WeakSet和WeakMap
WeakSet和WeakMap
(1)WeakSet和WeakMap一些相似的特點
1.WeakSet的成員只能是物件,WeakMap只接受物件(null除外)作為鍵名
let weakset = new WeakSet([1, 2, 3]);
//TypeError: Invalid value used in weak set
let weakmap=new WeakMap([[0,2],[0,3]]);
//TypeError: Invalid value used as weak map
//這裡的0為數字而不是物件
上面的程式碼均會報錯。
let weakmap=new WeakMap();
let key={};
weakmap.set(key,1);
console.log(weakmap);
//WeakMap { {}=>1 }
WeakMap只接受物件(null除外)作為鍵名,其他型別的值都可以作為鍵值。
2.不計入垃圾回收機制
WeakSet中的物件不計入垃圾回收機制,WeakMap中鍵名指向的物件不計入垃圾回收機制。
常見垃圾收集方式——標記清除
我們先來複習一下垃圾回收機制:在編寫javascript程式時,開發人員不用關心記憶體的使用問題,所需記憶體的分配以及無用記憶體的回收實現了自動管理,垃圾收集器會按照固定的時間間隔,找出那些不再繼續使用的變數,然後釋放其佔用的記憶體。
javascript中通常有兩種垃圾收集方式:
1.標記清除(Mark-and-Sweep)
2.引用計數(Reference-counting)
大多數瀏覽器使用的是標記清除策略,因為引用計數策略無法解決因為迴圈引用而造成記憶體洩露問題。
標記清除指的是:
當變數進入執行環境,就將這個變數標記為“進入環境”,當變數離開環境時,將其標記為“離開環境”。垃圾收集器在執行的時候會給儲存在記憶體中的所有變數加上標記(不同的瀏覽器可能使用不同的標記方式)。然後,它會去掉環境中的變數以及被環境中的變數引用的變數的標記。在此之後仍然被加上標記的變數將被視為準備刪除的變數,之後垃圾收集器執行的時候(不同瀏覽器垃圾收集器執行的時間間隔互有不同)銷燬帶標記的值並回收它們佔用的記憶體。
3.WeakSet和WeakMap沒有遍歷操作,沒有size屬性,沒有clear方法
WeakSet中的物件是弱引用,WeakMap的鍵名所引用的物件也是弱引用。垃圾回收機制不將它們的引用考慮在內。一旦不再需要,垃圾回收機制會自動將它們回收,不用手動刪除,避免了記憶體的洩露。
因為WeakSet的成員和WeakMap引用的鍵名可能會隨時消失,所以WeakSet和WeakMap沒有遍歷操作,也沒有size屬性,並且兩者都沒有clear方法。
對於WeakMap來說,弱引用的只是鍵名而不是鍵值
let weakmap = new WeakMap();
let key = {};
let obj = {"foo": 1};
weakmap.set(key, obj);
console.log(weakmap);
//WeakMap { {}=>{"foo":1} }
obj = null;
console.log(weakmap);
//WeakMap { {}=>{"foo":1} }
鍵值obj是正常引用的。所以,即使在WeakMap外部消除了obj的引用,WeakMap內部的引用依然存在。
let key = {};
let obj = {"foo": 1};
let weakmap = new WeakMap([[key,obj]]);
console.log(weakmap);
//WeakMap { {}=>{"foo":1} }
key = {"rrr":1};
obj = {"foo": 2};
console.log(weakmap);
//WeakMap { {}=>{"foo":1} }
即使在WeakMap外部改變了鍵名key和鍵值obj的外部引用,但在WeakMap內部依然引用的是構造時的引用值。
(2)WeakSet
WeakSet結構與Set類似,也是不重複的值的集合。
1.基本語法
WeakSet建構函式
任何具有iterable介面的物件都可以成為WeakSet建構函式的引數。以陣列為例,陣列的所有成員都會自動成為WeakSet例項物件的成員,而陣列的成員只能是物件。
let weakset = new WeakSet([[1, 2], [2, 3]]);
console.log(weakset);
// WeakSet [[1,2],[2,3]]
// WeakSet [[2,3],[1,2]]
//都有可能出現
let set=new Set([[1, 2], [2, 3]]);
console.log(set);
//Set [[1,2],[2,3]]
以上程式碼可以看到WeakSet中的元素順序和Set中的元素順序是不同的。
WeakSet中元素順序並不是確定的,當使用add()後,元素的順序還會發生改變:
let weakset = new WeakSet([[1, 2], [2, 3]]);
console.log(weakset);
// WeakSet [[4,5],[1,2],[2,3]]
// WeakSet [[2,3],[4,5],[1,2]]
//等等各種順序都有可能出現
weakset.add([4,5]);
console.log(weakset);
// WeakSet [[4,5],[1,2],[2,3]]
// WeakSet [[2,3],[4,5],[1,2]]
//等等各種順序都有可能出現
兩次執行相同的程式碼,在WeakSet中元素的順序有可能是不同的。
2.操作方法
WeakSet沒有遍歷操作,沒有size屬性,沒有clear方法。
add(value)
向WeakSet例項新增一個新成員。
delete(value)
清除WeakSet例項的指定成員。
has(value)
返回一個布林值,表示某個值是否在WeakSet例項中。
3.使用WeakSet的例子
const foos = new WeakSet()
class Foo {
constructor() {
foos.add(this)
}
method () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method 只能在Foo的例項上呼叫!');
}
}
}
上面程式碼保證了Foo的例項方法,只能在Foo的例項上呼叫。這裡使用 WeakSet 的好處是,foos對例項的引用,不會被計入記憶體回收機制,所以刪除例項的時候,不用考慮foos,也不會出現記憶體洩漏。
(3)WeakMap
WeakMap結構與Map結構類似也用於生成鍵值對的集合。
1.基本語法
WeakMap建構函式
WeakMap可以接受一個數組,作為建構函式的引數:
let k1 = [1, 2];
let k2 = [2, 3];
let weakmap = new WeakMap([[k1, 123], [k2, 234]]);
console.log(weakmap);
//WeakMap { [2,3]=>234,[1,2]=>123 }
//WeakMap { [1,2]=>123,[2,3]=>234 }
//都有可能出現
let map=new Map([[k1, 123], [k2, 234]]);
console.log(map);
//WeakMap { [2,3]=>234,[1,2]=>123 }
以上程式碼可以看到WeakMap中的元素順序和Map中的元素順序是不同的。
WeakMap中元素順序並不是確定的,當使用set()後,元素的順序還會發生改變:
let k1 = [1, 2];
let k2 = [2, 3];
let weakmap = new WeakMap([[k1, 123], [k2, 234]]);
console.log(weakmap);
//WeakMap { [2,3]=>234,[1,2]=>123,[4,5]=>456}
//WeakMap { [2,3]=>234,[4,5]=>456,[1,2]=>123}
//WeakMap { [4,5]=>456,[2,3]=>234,[1,2]=>123}
//等等各種順序都有可能出現
weakmap.set([4,5],456);
console.log(weakmap);
//WeakMap { [2,3]=>234,[1,2]=>123,[4,5]=>456}
//WeakMap { [2,3]=>234,[4,5]=>456,[1,2]=>123}
//WeakMap { [4,5]=>456,[2,3]=>234,[1,2]=>123}
//等等各種順序都有可能出現
兩次執行相同的程式碼,在WeakMap中元素的順序有可能是不同的。
2.操作方法
WeakMap沒有遍歷操作,沒有size屬性,沒有clear方法。
WeakMap只有以下四種方法:
get()
set()
has()
delete()
3.使用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++;
}, false);
上面程式碼中,myElement是一個 DOM 節點,每當發生click事件,就更新一下狀態。我們將這個狀態作為鍵值放在 WeakMap 裡,對應的鍵名就是myElement。一旦這個 DOM 節點刪除,該狀態就會自動消失,不存在記憶體洩漏風險。
實現註冊監聽事件的listener物件
const listener = new WeakMap();
listener.set(element1, handler1);
listener.set(element2, handler2);
element1.addEventListener("click",listener.get(element1),false);
element2.addEventListener("click",listener.get(element2),false);
上面的程式碼中,監聽函式放在WeakMap裡面。一旦DOM物件消失,與它繫結的監聽函式也會自動消失。
部署私有屬性
const _counter = new WeakMap();
const _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)();
}
}
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
上面程式碼中,Countdown類的兩個內部屬性_counter和_action,是例項的弱引用,所以如果刪除例項,它們也就隨之消失,不會造成記憶體洩漏。