JavaScript ES6 Set and WeakSet Object 物件
ES6 新增了 Set 和 WeakSet 數據結構。
Set
Set 有點像是陣列 (array),Set 中的元素 (element) 可以是任何資料型態,兩者不同的地方在於 Set 中所有的值都是唯一的 (unique values),不會有重複的值,當你存入重複值 (duplicate values) 會被忽略。
當你向 Set 中加入新元素時,Set 內部會用 ===
來判斷是否有重複值,唯一的例外是 NaN
會被判斷作是重複的值,雖然 NaN !== NaN
。
而 Set 中的資料也是有序的,當你遍歷一個 Set 資料結構時,會依照先前寫入的順序 (insertion order)。
建立 Set 物件的語法:
// 建立一個空 Set
new Set()
// 建立一個 Set 並初始化
new Set(iterable)
使用範例:
var mySet = new Set();
// add() 用來新增元素
mySet.add(1);
mySet.add(5);
mySet.add('some text');
var o = {a: 1, b: 2};
mySet.add(o);
// 因為 o !== {a: 1, b: 2} 所以可以加入
mySet.add({a: 1, b: 2});
// has() 用來判斷 Set 中有沒某個值
// true
mySet.has(1);
// false
mySet.has(3);
// true
mySet.has(5);
// true
mySet.has(Math.sqrt(25));
// true
mySet.has('Some Text'.toLowerCase());
// true
mySet.has(o);
Set 的構造函數 (constructor) 可以傳入一個陣列當作初始化參數:
var mySet = new Set(['value1', 'value2', 'value3']);
// true
mySet.has('value1');
// Set {"value1", "value2", "value3"}
mySet;
Set 物件的屬性和方法 Properties and Methods
Set.prototype.size
用來取得 Set 物件的大小,總共有幾個元素。
var mySet = new Set();
mySet.add(1);
mySet.add(5);
mySet.add('some text')
// 3
mySet.size;
Set.prototype.add(value)
用來新增元素,add() 方法會返回 Set 本身。
var mySet = new Set();
mySet.add(1);
mySet.add(5)
mySet.add('some text');
// Set {1, 5, "some text"}
mySet;
因為 add() 方法會返回 Set 本身,所以你可以用 chaining 的寫法:
var mySet = new Set();
mySet.add(1)
.add(5)
.add('some text');
// Set {1, 5, "some text"}
mySet;
Set.prototype.has(value)
返回一個 boolean,判斷 Set 中有沒某個值。
var mySet = new Set();
mySet.add('foo');
// true
mySet.has('foo');
// false
mySet.has('bar');
Set.prototype.delete(value)
刪除某個值,如果刪除成功會返回 true,反之返回 false。
var mySet = new Set();
mySet.add('foo');
// false
mySet.delete('bar');
// true
mySet.delete('foo');
// false
mySet.has('foo');
Set.prototype.clear()
用來清空 Set,刪除所有的元素,沒有返回值。
var mySet = new Set();
mySet.add(1);
mySet.add('foo');
// 2
mySet.size;
// true
mySet.has('foo');
mySet.clear();
// 0
mySet.size;
// false
mySet.has('bar');
Set.prototype.keys() Set.prototype.values()
keys() 和 values() 行為是一樣的,返回一個 Iterator 物件表示所有的元素值。
Set 資料結構中沒有 key,只有 value,或說在 Set 中 key 等於 value。
var mySet = new Set([50, 'a', {foo: 'bar'}]);
for (let v of mySet.values()) {
console.log(v);
}
// 依序輸出 50 a {foo: "bar"}
for (let k of mySet.keys()) {
console.log(k);
}
Set.prototype.entries()
返回一個 Iterator 物件,其中每一個值是 [value, value] 結構的陣列。
var mySet = new Set();
mySet.add('foobar');
mySet.add(1);
mySet.add('baz');
for (let item of mySet.entries()) {
console.log(item);
}
// 依序輸出
// ["foobar", "foobar"]
// [1, 1]
// ["baz", "baz"]
Set.prototype.forEach(callbackFn[, thisArg])
forEach() 方法可以用來遍歷 Set。
其中第一個參數 callbackFn 是一個函數,有三個參數:
- value
- value
- Set 物件本身
為什麼要有兩個同樣的 value?是為了和 Map 及 Array 的 forEach 用法一致。
第二個參數 thisArg 不是必要的參數,表示 this 指向的物件。
function logSetElements(value1, value2, set) {
console.log('s[' + value1 + '] = ' + value2);
}
new Set(['foo', 'bar', undefined]).forEach(logSetElements);
// 依序輸出
// "s[foo] = foo"
// "s[bar] = bar"
// "s[undefined] = undefined"
for...of
也可以用 for...of 來遍歷 Set。
var mySet = new Set([50, 'a', {foo: 'bar'}]);
for (let v of mySet) {
console.log(v);
}
// 依序輸出 50 a {foo: "bar"}
WeakSet
WeakSet 和 Set 資料結構基本上是類似的,唯一的區別是 WeakSet 只接受 object 當作元素值 (除了 null 以外,值也不行是 null,會被當作是 undefined 看待),基本資料型態 (primitive data types) 都不能被當作是值。
WeakSet 中的 object 不會被垃圾回收機制 (garbage collection) 計入參考,這也就是 weak 的意思 - 弱引用 (weakly reference)。
因為 weakly reference,所以 WeakSet 中的 object 可能隨時會被自動回收 (garbage collected),而當 object 被回收後,其所對應的元素也會自動被刪除,所以說用 WeakSet 可以方便地避免內存泄露 (memory leak) 的問題。
var ws = new WeakSet();
// 錯誤
// TypeError: Invalid value used in weak set
ws.add(100);
因為 WeakSet weakly reference 的特性,WeakSet 不支援遍歷類型的操作,只支援三個方法 add(), has(), delete()。
WeakSet 的操作基本上和 Set 相似:
var ws = new WeakSet();
var obj = {};
var foo = {};
ws.add(window);
ws.add(obj);
// true
ws.has(window);
// false
ws.has(foo);
// 從 WeakSet 中刪除 window
ws.delete(window);
// false
ws.has(window);
再舉一個 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 你就可以在刪除 Foo 物件時,不用管 foos 中的引用,也不會出現 memory leak。