一、Set
1.1 基本用法
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
Set可以接收一個數組或實現了Iterable接口的對象作爲參數,用來初始化Set。例如:
const s1 = new Set([1, 2, 3, 4, 4]);
const s2 = new Set(document.querySelectorAll('div'));
利用上面Set特點,可以實現把數組重複的元素去除。
var s = [...new Set([1, 1, 2, 2, 3, 3])]
s.map(x => console.log(x)) // 分別輸出1, 2, 3
Set 內部判斷兩個值是否不同,使用的算法叫做“Same-value-zero equality”,它類似於精確相等運算符(===)。因此,5和’5’被認爲是兩個不同的元素。
1.2 基本屬性和方法
Set提供了以下屬性:
Set.prototype.constructor:構造函數,默認是Set函數;
Set.prototype.size:返回set實例的成員個數;
Set提供了以下方法:
Set.prototype.add(value):向Set添加一個元素,並返回Set本身;
Set.prototype.delete(value):刪除某個元素,返回一個布爾值,表示是否刪除成功;
Set.prototype.has(value):返回一個布爾值,表示該值是否爲Set成員;
Set.prototype.clear():清除Set所有成員,沒有返回值;
Set.prototype.values():返回所有值的遍歷器;
Set.prototype.keys():返回所有鍵的遍歷器,但是由於Set沒有鍵只有值,所以該方法和values方法的行爲是一致的;
var s = new Set()
s.add(10).add(20).add(30)
// 方式一
for (let i of s.keys()) {
alert(i)
}
// 方式二
for (let i of s.values()) {
alert(i)
}
// 方式三
for (let i of s) {
alert(i)
}
Set 結構的實例默認可遍歷,所以上面三種方式遍歷Set,得到的結果相同。
Set.prototype.entries():返回所有鍵值對的遍歷器,所以每次輸出一個數組,它的兩個成員完全相等;
Set.prototype.forEach():使用回調函數遍歷Set中每個成員;
var arr = [1, 1, 2, 3, 4, 4];
new Set(arr).forEach(x => alert(x))
需要特別指出的是,Set的遍歷順序就是插入順序。
另外,數組的map和filter方法也可以間接用於 Set 了。
let arr = [1, 1, 2, 3, 4, 4]
let s1 = new Set(arr.map(x => x * 2))
s1.forEach(x => alert(x)) // 2 4 6 8
let s2 = new Set(arr.filter(x => (x % 2) == 0))
s2.forEach(x => alert(x)) // 2 4
1.3 Set與數組之間的轉換:
將數組轉換成Set:
new Set(Array對象)
將Set轉換成數組:
// 方式一:使用展開運算符
[...Set對象]
// 方式二:使用Array.from函數
Array.from(Set對象)
1.4 Set和WeakSet的區別
WeakSet與Set的結構,它裏面的元素也是不重複,它們在用法上也非常類似。但是與Set不同,WeakSet的成員只能夠是對象,而不能是其他類型的值。
const ws = new WeakSet();
ws.add(1) // TypeError: Invalid value used in weak set
其次,WeakSet中的對象都是弱引用。如果其他對象都不再引用該對象,那麼垃圾回收機制會自動回收該對象所佔用的內存,不考慮該對象還存在於 WeakSet 之中。
基於上面特點,WeakSet的成員不適合被引用。因此,ES6規定WeakSet不可遍歷。因此遍歷機制無法保證成員的存在。
二、Map
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),但是傳統上只能用字符串當作鍵。這給它的使用帶來了很大的限制。
爲了解決這個問題,ES6 提供了 Map 數據結構。它類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object 結構提供了“字符串—值”的對應,Map 結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。如果你需要“鍵值對”的數據結構,Map 比 Object 更合適。
2.1 Map的基本使用
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
上面代碼使用 Map 結構的set方法,將對象o當作m的一個鍵,然後又使用get方法讀取這個鍵,接着使用delete方法刪除了這個鍵。
Map也可以接受一個數組作爲參數。每一個數組元素都是一個表示鍵值對的數組。例如:
const map = new Map([
['name', '張三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "張三"
map.has('title') // true
map.get('title') // "Author"
上面代碼在新建 Map 實例時,就指定了兩個鍵name和title。
事實上,不僅僅是數組,任何具有 Iterator 接口、且每個成員都是一個雙元素的數組的數據結構都可以當作Map構造函數的參數。這就是說,Set和Map都可以用來生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
上面代碼中,我們分別使用 Set 對象和 Map 對象,當作Map構造函數的參數,結果都生成了新的 Map 對象。
如果對同一個鍵多次賦值,後面的值將覆蓋前面的值。
const map = new Map();
map.set(1, 'aaa').set(1, 'bbb');
map.get(1) // "bbb"
如果讀取一個未知的鍵,則返回undefined。
new Map().get('asfddfsasadf')
// undefined
注意,Map 的鍵實際上是跟內存地址綁定的,只要內存地址不一樣,就視爲兩個鍵。
如果 Map 的鍵是一個簡單類型的值(如數字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視爲一個鍵,比如0和-0就是一個鍵,布爾值true和字符串true則是兩個不同的鍵。另外,undefined和null也是兩個不同的鍵。雖然NaN不嚴格相等於自身,但 Map 將其視爲同一個鍵。
2.2 Map屬性和方法
(1)size屬性
size屬性返回Map中成員個數。
(2)Map.prototype.set(key, value)
set方法設置鍵名key對應的鍵值爲value,然後返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。
(3)Map.prototype.get(key)
get方法讀取key對應的鍵值,如果找不到key,返回undefined。
(4)Map.prototype.has(key)
has方法返回一個布爾值,表示某個鍵是否在當前 Map 對象之中。
(5)Map.prototype.delete(key)
delete方法刪除某個鍵,返回true。如果刪除失敗,返回false。
(6)Map.prototype.clear()
clear方法清除所有成員,沒有返回值。
(7)遍歷方法keys()、values()、entries()和forEach()
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script>
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
// 遍歷map所有key
for (let key of map.keys()) {
console.log(key);
}
// 遍歷map所有value
for (let value of map.values()) {
console.log(value);
}
// 遍歷map所有鍵值對
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// 等同於使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
</script>
</body>
</html>
Map 提供了遍歷器屬性(Symbol.iterator),因此遍歷map出來的就是鍵值對。
需要特別注意的是,Map 的遍歷順序就是插入順序。
2.3 Map與其他數據結構的轉換
(1)Map轉數組
把map轉換成數組最簡單方式是使用展開運算符(…)。
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
(2)數組轉Map
將數組傳入 Map 構造函數,就可以轉爲 Map。
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
每一個數組元素都包含兩個元素,第一個元素是鍵,第二個元素是值。
(3)Map轉對象
如果所有 Map 的鍵都是字符串,它可以無損地轉爲對象。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
(4)對象轉Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
let map = objToStrMap({yes: true, no: false})
alert(map.get('yes')); // true
(5)Map轉JSON
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
let json = strMapToJson(myMap);
alert(json);
(6)JSON轉Map
JSON 轉爲 Map,正常情況下,所有鍵名都是字符串。
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
但是,有一種特殊情況,整個 JSON 就是一個數組,且每個數組成員本身,又是一個有兩個成員的數組。這時,它可以一一對應地轉爲 Map。這往往是 Map 轉爲數組 JSON 的逆操作。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
2.4 WeakMap
WeakMap與Map的結構類似,也是用於生成鍵值對的集合。例如:
// WeakMap 可以使用 set 方法添加成員
const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2
// WeakMap 也可以接受一個數組,
// 作爲構造函數的參數
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"
WeakMap與Map的區別有兩點:
第一,WeakMap只接受對象作爲鍵名(null除外),不接受其他類型的值作爲鍵名。
第二,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。
例如:
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
上面的 DOM 節點對象的引用計數是1,而不是2。這時,一旦消除對該節點的引用,它佔用的內存就會被垃圾回收機制釋放。Weakmap 保存的這個鍵值對,也會自動消失。
WeakMap的專用場合就是,它的鍵所對應的對象,可能會在將來消失。WeakMap結構有助於防止內存泄漏。
注意,WeakMap 弱引用的只是鍵名,而不是鍵值。鍵值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
obj = null;
wm.get(key) // Object {foo: 1}
上面代碼中,鍵值obj是正常引用。所以,即使在 WeakMap 外部消除了obj的引用,WeakMap 內部的引用依然存在。所以最後一行代碼仍然能夠輸出{foo: 1}。
WeakMap的基本語法
WeakMap 與 Map 在 API 上的區別主要是兩個,一是沒有keys()、values()和entries()等遍歷方法,也沒有size屬性。WeakMap只有四個方法可用:get()、set()、has()、delete()。