1. 程式人生 > >ES6 WeakSet和WeakMap

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,是例項的弱引用,所以如果刪除例項,它們也就隨之消失,不會造成記憶體洩漏。

參考文獻:《ECMAScript 6 入門》阮一峰