Set和WeakSet數據結構

Set

基本用法

ES6 提供了新的數據結構Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。算法

Set 自己是一個構造函數,用來生成 Set 數據結構:數組

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4

上面代碼經過add方法向 Set 結構加入成員,結果代表 Set 結構不會添加劇復的值。數據結構

Set 函數能夠接受一個數組(或者具備iterable接口的其餘數據結構)做爲參數,用來初始化,下面是兩種利用set進行數組去重的方式:函數

const arr = [1,2,2,3];
//方式一
console.log(Array.from(new Set(arr)));
//方式二
console.log([...new Set(arr)]);

向 Set 加入值的時候,不會發生類型轉換,因此5和"5"是兩個不一樣的值。Set 內部判斷兩個值是否不一樣,使用的算法叫作「Same-value equality」,它相似於精確相等運算符(===),主要的區別是NaN等於自身,而精確相等運算符認爲NaN不等於自身。this

let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}

上面代碼向 Set 實例添加了兩個NaN,可是隻能加入一個。這代表,在 Set 內部,兩個NaN是相等。prototype

Set 實例的屬性和方法

Set 結構的實例有如下屬性:code

  • Set.prototype.constructor:構造函數,默認就是Set函數。
  • Set.prototype.size:返回Set實例的成員總數。

Set 實例的方法分爲兩大類:操做方法(用於操做數據)和遍歷方法(用於遍歷成員)。下面先介紹四個操做方法:對象

  • add(value):添加某個值,返回Set結構自己。
  • delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
  • has(value):返回一個布爾值,表示該值是否爲Set的成員。
  • clear():清除全部成員,沒有返回值。

下面是一個對比,看看在判斷是否包括一個鍵上面,Object結構和Set結構的寫法不一樣:接口

// 對象的寫法
const properties = {
  'width': 1,
  'height': 1
};

if (properties[someName]) {
  // do something
}

// Set的寫法
const properties = new Set();

properties.add('width');
properties.add('height');

if (properties.has(someName)) {
  // do something
}

遍歷操做

Set 結構的實例有四個遍歷方法,能夠用於遍歷成員:內存

  • keys():返回鍵名的遍歷器
  • values():返回鍵值的遍歷器
  • entries():返回鍵值對的遍歷器
  • forEach():使用回調函數遍歷每一個成員

1. keys(),values(),entries()

keys方法、values方法、entries方法返回的都是遍歷器對象。因爲 Set結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys方法和values方法的行爲徹底一致:

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

Set 結構的默認遍歷器生成函數就是它的values方法,這意味着,能夠省略values方法,直接用for...of循環遍歷 Set:

Set.prototype[Symbol.iterator] === Set.prototype.values
// true

let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
  console.log(x);
}
// red
// green
// blue

2. forEach()

Set 結構的實例與數組同樣,也擁有forEach方法,用於對每一個成員執行某種操做,沒有返回值

set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

3.遍歷的應用

擴展運算符(...)內部使用for...of循環,結合數組的map和filter方法,很容易實現交集、並集、差集:

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 並集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

若是想在遍歷操做中,同步改變原來的Set結構,目前沒有直接的方法,但有兩種變通方法。一種是利用原 Set 結構映射出一個新的結構,而後賦值給原來的 Set 結構;另外一種是利用Array.from方法:

// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6

// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6

WeakSet

含義

WeakSet 結構與 Set 相似,也是不重複的值的集合。可是,它與 Set 有兩個區別,首先,WeakSet 的成員只能是對象,而不能是其餘類型的值。

const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set

上面代碼試圖向 WeakSet 添加一個數值和Symbol值,結果報錯,由於 WeakSet 只能放置對象。

其次,WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用,也就是說,若是其餘對象都再也不引用該對象,那麼垃圾回收機制會自動回收該對象所佔用的內存,不考慮該對象還存在於 WeakSet 之中。

這是由於垃圾回收機制依賴引用計數,若是一個值的引用次數不爲0,垃圾回收機制就不會釋放這塊內存。結束使用該值以後,有時會忘記取消引用,致使內存沒法釋放,進而可能會引起內存泄漏。WeakSet裏面的引用,都不計入垃圾回收機制,因此就不存在這個問題。所以,WeakSet適合臨時存放一組對象,以及存放跟對象綁定的信息。只要這些對象在外部消失,它在 WeakSet 裏面的引用就會自動消失。

const weakSet = new WeakSet();
let obj = {};
weakSet.add(obj);
console.log(weakSet.has(obj));//true
obj = null;
console.log(weakSet.has(obj));//false

因爲上面這個特色,WeakSet的成員是不適合引用的,由於它會隨時消失。另外,因爲WeakSet內部有多少個成員,取決於垃圾回收機制有沒有運行,運行先後極可能成員個數是不同的,而垃圾回收機制什麼時候運行是不可預測的,所以 ES6規定WeakSet 不可遍歷。

語法

做爲構造函數,WeakSet能夠接受一個數組或相似數組的對象做爲參數。該數組的全部成員,都會自動成爲 WeakSet 實例對象的成員。

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}

上面代碼中,a是一個數組,它有兩個成員,也都是數組。將a做爲WeakSet構造函數的參數,a的成員會自動成爲 WeakSet 的成員。

注意,是a數組的成員成爲WeakSet的成員,而不是a數組自己。這意味着,數組的成員只能是對象。

const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)

WeakSet 結構有如下三個方法:

  • WeakSet.prototype.add(value):向 WeakSet 實例添加一個新成員
  • WeakSet.prototype.delete(value):清除 WeakSet 實例的指定成員
  • WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在 WeakSet 實例之中。

WeakSet沒有size屬性,沒有辦法遍歷它的成員:

ws.size // undefined
ws.forEach // undefined

ws.forEach(function(item){ console.log('WeakSet has ' + item)})
// TypeError: undefined is not a function

WeakSet 不能遍歷,是由於成員都是弱引用,隨時可能消失,遍歷機制沒法保證成員的存在,極可能剛剛遍歷結束,成員就取不到了。WeakSet 的一個用處,是儲存 DOM 節點,而不用擔憂這些節點從文檔移除時,會引起內存泄漏。

下面是 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,也不會出現內存泄漏。

相關文章
相關標籤/搜索