ECMAScript 6之Set 和 Map 數據結構

1.Set

ES6 提供了新的數據結構 Set。它類似於數組,但是成員的值都是唯一的,沒有重複的值。

Set本身是一個構造函數,用來生成 Set 數據結構。

let s = new Set();

typeof s; // "object"

Set函數可以接受一個數組(或者具有 iterable 接口的其他數據結構)作爲參數,用來初始化。

// 接受數組作爲參數
let x = new Set(["jidi", "rycony", "xuxiake2019"]);
x; // Set(3) {"jidi", "rycony", "xuxiake2019"}

// 接受具有iterable接口的數據結構作爲參數
let y = new Set("jidi");
y; // Set(3) {"j", "i", "d"}

上面代碼中,變量y最終只有三個元素,那是因爲Set數據結構成員的值都是唯一的。

向 Set 加入值的時候,不會發生類型轉換。Set 內部判斷兩個值是否不同,使用的算法叫做“Same-value-zero equality”。

let x = new Set();

// set內部認爲NaN等於NaN
x.add(NaN);
x.add(NaN);
x; // Set(1) {NaN}

// set添加值,不會發生類型轉換
x.add(1);
x.add("1");
x; // Set(3) {NaN, 1, "1"}

// set內部+0等於-0
x.add(+0);
x.add(-0);
x; // Set(4) {NaN, 1, "1", 0}

1.1 Set 實例的屬性和方法

Set結構的實例有以下屬性:

  • Set.prototype.constructor:構造函數,默認是Set函數。
  • Set.prototype.size:返回Set實例的成員總數。
let x = new Set([1, 2, 3]);

// 獲得實例構造函數,默認爲Set
x.constructor; // ƒ Set() { [native code] }
// 獲得set實例的成員數
x.size; // 3

Set結構的實例有以下方法:

操作數據相關方法

  • Set.prototype.add(val):添加某個值,返回 Set 結構本身。
  • Set.prototype.delete(val):刪除某個值,返回一個布爾值,表示刪除是否成功。
  • Set.prototype.has(val):返回一個布爾值,表示該值是否爲Set的成員。
  • Set.prototype.clear():清除所有成員,沒有返回值。
let x = new Set();

// 向set添加值,返回set結構
x.add("jidi");
x.add("rycony");
x; // Set(2) {"jidi", "rycony"}

// 刪除,返回布爾值,表示是否刪除成功
x.delete("jidi"); // true
x; // Set(1) {"rycony"}

// 判斷一個值是否在set中
x.has("rycony"); // true

// 清空set
x.clear();
x; // Set(0) {}

遍歷數據相關方法

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

(1)keys(),values(),entries()
keys()values()entries()三個方法返回的都是遍歷器對象。由於Set結構沒有鍵名,只有鍵值,keys()values()兩個方法的行爲一致。

let x = new Set("jidi");

// 獲取鍵名
x.keys(); // SetIterator {"j", "i", "d"}

// 獲取鍵值
x.values(); // SetIterator {"j", "i", "d"}

//獲取鍵值對
x.entries(); // SetIterator {"j" => "j", "i" => "i", "d" => "d"}

Set結構的實例默認可遍歷,它的默認遍歷器生成函數就是values()方法。

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

由於Set結構實例默認遍歷器生成函數就是values()方法,我們可以省略values()方法,遍歷Set結構實例。

let x = new Set("jidi");

for(let m of x.values()) {
	console.log(m);
}
// 等價於
for(let m of x) {
	console.log(m);
}

(2) forEach()
Set 結構的實例擁有forEach方法,用於對每個成員執行某種操作,沒有返回值。

let x = new Set([1, 2, 3, 4]);
let y = [];

x.forEach((value,key) => y.push(value * key));
y; // [1, 4, 9, 16]

上面代碼中,forEach()方法的參數是一個函數。該函數可以接受三個參數,依次是:鍵值,鍵名,集合本身。

forEach()方法還可以有第二個參數,用來綁定處理函數內部的this對象。

1.2 Set 實例與擴展運算符結合使用

擴展運算符(...)內部使用的是for ...of循環,也可以用於Set結構實例。

let x = new Set(["jidi", "rycony", "xuxiake2016"]);

// 通過擴展運算符將Set結構實例轉爲數組
[...x]; // ["jidi", "rycony", "xuxiake2016"]

擴展運算符(...)與Set結合使用,可以去除數組的重複成員。

let x = [1, 2, 3, 4, 3, 2];

x = [...new Set(x)]; // [1, 2, 3, 4]

通過擴展運算符,數組的mapfilter方法也能間接被Set結構實例使用。

let x = [1, 2, 3, 4, 5, 6];
let set = new Set(x);

// 使用數組的map方法
set = new Set([...set].map(value => value + 1)); // Set(6) {2, 3, 4, 5, 6, 7}

// 使用數組的filter方法
set = new Set([...set].filter(x => x % 2 === 0 )); // Set(3) {2, 4, 6}

Set結構實例與擴展運算符結合使用,可以很容易實現並集,交集,差集。

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

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

// 交集
let y = new Set([...a].filter(value => b.has(value))); // Set(3) {2, 3, 4}

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

如果要在遍歷操作中,同步改變原來的 Set 結構,目前有兩種變通方法:

  • 利用原 Set 結構映射出一個新的結構,然後賦值給原來的 Set 結構
  • 利用Array.from方法,將一個類似數組的結構變爲真正的數組,作爲Set的參數
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x + 1)); // Set(3) {2, 3, 4}

// 方法二
set = new Set([1, 2, 3]);
set = new Set(Array.from(set, x => x * x)); // Set(3) {1, 4, 9}

2. WeakSet

WeakSet 結構與 Set 類似,也是不重複的值的集合。但是,它與 Set 有兩個區別:

  • WeakSet 的成員只能是對象,而不能是其他類型的值
  • WeakSet 中的對象都是弱引用
let weakSet = new WeakSet();

// WeakSet成員只能是對象
weakSet.add(1); // Uncaught TypeError: Invalid value used in weak set at WeakSet.add
weakSet.add(true); //  TypeError

weakSet.add({name: "jidi"}); // 成功添加對象

WeakSet 是一個構造函數,可以使用new命令,創建 WeakSet 數據結構。

作爲構造函數,WeakSet 可以接受一個數組或類似數組的對象作爲參數。(具有 Iterator 接口的對象,都可以作爲 WeakSet 的參數。)該數組的所有成員,都會自動成爲 WeakSet 實例對象的成員。

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

上面代碼中,數組a的成員也是數組,將a作爲WeakSet的參數,a的成員會自動成爲WeakSet實例的成員。這意味着,數組或類似數組的對象作爲參數時,其內部成員必須是對象

let a = [1, 2];
let weakSet = new WeakSet(a); // Uncaught TypeError: Invalid value used in weak set at WeakSet.add at new WeakSet

2.1 WeakSet實例方法

WeakSet 結構有以下三個方法:

  • WeakSet.prototype.add(obj):向 WeakSet 實例添加一個新成員
  • WeakSet.prototype.delete(obj):清除 WeakSet 實例的指定成員
  • WeakSet.prototype.has(obj):返回一個布爾值,表示某個值是否在 WeakSet 實例之中
let x = { name: "jidi" };
let y = { name: "rycony" };
let z = { name: "xuxaike2016" };

let weakSet = new WeakSet();

// 添加成員
weakSet.add(x);
weakSet.add(y);
weakSet.add(z);

// 刪除成員
weakSet.delete(x); // true

// 判斷某個值是否在WeakSet
weakSet.has(x); // false

WeakSet 不能遍歷,是因爲成員都是弱引用,隨時可能消失,遍歷機制無法保證成員的存在。而且WeakSet 也沒有size屬性。

3. Map

JavaScript 的對象(Object),本質上是鍵值對的集合,但是傳統上只能用字符串當作鍵。
ES6 提供了 Map 數據結構。它類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值都可以當作鍵。

Map本身是一個構造函數,用來生成 Map數據結構。

let map = new Map();

作爲構造函數,Map也可以接受一個數組作爲參數。該數組的成員是一個個表示鍵值對的數組。

let map = new Map([
  ["name", "jidi"]
]);

map.size; // 1

任何具有 Iterator 接口、且每個成員都是一個雙元素的數組的數據結構都可以當作Map構造函數的參數。

let set = new Set([["name", "jidi"], ["age", 22]]);

let map = new Map(set); // Map(2) {"name" => "jidi", "age" => 22}

如果對同一個鍵多次賦值,後面的值將覆蓋前面的值。

let set = new Set([["name", "jidi"], ["name", 22]]);
let map = new Map(set);  // Map(1) {"name" => 22}

如果讀取一個未知的鍵,則返回undefined

new Map().get("name"); // undefined

只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵。

let a = { name: "jidi" };
let map = new Map();

// 鍵名爲基本數據類型
map.set("name", "jidi"); // Map(1) {"name" => "jidi"}
map.get("name"); // "jidi"

// 鍵名爲對象
map.set(a, 22);
map.get({name: "jidi"}); // undefined
map.get(a); // 22

上面代碼中,鍵名爲基本數據類型時,只要兩個值相等,則Map將其視爲一個鍵;當鍵名爲對象時,內存地址一樣纔會視爲一個鍵。

3.1 Map實例屬性和方法

Map結構實例有以下屬性:

  • Map.prototype.size:返回Map實例的成員總數
new Map([[name, "jidi"]]).size; // 1

Map結構的實例有以下方法:

操作數據相關方法

  • Map.prototype.set(key, value):設置鍵名key對應的鍵值爲value,然後返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。
  • Map.prototype.get(key):讀取key對應的鍵值,如果找不到key,返回undefined
  • Map.prototype.has(key):返回一個布爾值,表示某個鍵是否在當前 Map對象之中。
  • Map.prototype.delete(key):刪除某個鍵,刪除成功返回true。如果刪除失敗,返回false
  • Map.prototype.clear():清除所有成員,沒有返回值。
let map = new Map();

map.set('name', "jidi"); // 鍵是字符串
map.set(22, "age"); // 鍵是數值
map.set(undefined, ''); // 鍵是 undefined
map: // Map(3) {"name" => "jidi", 22 => "age", undefined => ""}

map.get(22); // 鍵名存在,返回鍵值"age"
map.get(23);  // 鍵名不存在,返回undefined

map.has(22); // true
map.has(23); // false

map.delete(22); // 刪除成功返回true
map; // Map(2) {"name" => "jidi", undefined => ""}

map.clear(); // 清除所有成員
map; // Map(0) {}

遍歷數據相關方法

  • Map.prototype.keys():返回鍵名的遍歷器。
  • Map.prototype.values():返回鍵值的遍歷器。
  • Map.prototype.entries():返回所有成員的遍歷器。
  • Map.prototype.forEach():遍歷 Map 的所有成員。
let map = new Map([
  ["name", "jidi"],
  ["age",  22],
]);

// 遍歷鍵名
for (let key of map.keys()) {
  console.log(key);
}
// "name" "age"

// 遍歷鍵值
for (let value of map.values()) {
  console.log(value);
}
// "jidi" 22

// 遍歷鍵值對
for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "name" "jidi"
// "age" 22

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "name" "jidi"
// "age" 22

// 等同於使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "name" "jidi"
// "age" 22

上面代碼中,最後一個例子,表明Map 結構的默認遍歷器接口其實就是entries方法。

Map[Symbol.iterator] === Map.entries; // true

4. WeakMap

WeakMap結構與Map結構類似,也是用於生成鍵值對的集合。但是,它與 Map有兩個區別:

  • WeakMap只接受對象作爲鍵名(null除外),不接受其他類型的值作爲鍵名。
  • WeakMap的鍵名所引用的對象都是弱引用。
// WeakMap 可以使用 set 方法添加成員
let weakMap = new WeakMap();
let x = { name: "jidi"};
weakMap.set(x, 22);
weakMap.get(x); // 22

// WeakMap 也可以接受一個數組,作爲構造函數的參數
let a1 = [1, 2, 3];
let a2 = [4, 5, 6];
weakMap = new WeakMap([[a1, "jidi"], [a2, "rycony"]]);
weakMap.get(a2); // "rycony"

// 鍵名必須是對象
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
map.set(null, 2); // TypeError: Invalid value used as weak map key

4.1 WeakMap實例方法

WeakMap 與 Map 在 API 上的區別主要是兩個:沒有遍歷操作;也沒有size屬性。 因此,WeakMap只有四個方法可用:

  • WeakMap.prototype.get(key):讀取key對應的鍵值,如果找不到key,返回undefined
  • WeakMap.prototype.has(key):返回一個布爾值,表示某個鍵是否在當前 Map對象之中。
  • WeakMap.prototype.delete(key):刪除某個鍵,刪除成功返回true。如果刪除失敗,返回false
  • WeakMap.prototype.set(key, value):設置鍵名key對應的鍵值爲value,然後返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。

5. 參考鏈接

本篇博文是我自己學習筆記,原文請參考:ECMAScript 6 入門
如有問題,請及時指出!
歡迎溝通交流,郵箱:[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章