帶你重學ES6 | Set和Map

ES6 新增了兩個數據結構,一個是 set,另一個是 map。前端

一、set

在《你不知道的 JavaScript(下卷)》中是這麼定義的:set 是一個值的集合,其中的值惟一(重複會被忽略)。 它相似於數組,可是每一個成員的值是惟一的。 set 是一個構造函數,能夠經過 new 來建立一個 set 實例。git

let set = new Set([1, 2, 3, 1, 4]);
console.log(set); // {1, 2, 3, 4} 複製代碼

上述能夠看出,new Set 會自動過濾掉重複值,並返回一個集合,咱們能夠利用這個特性,來寫出簡單的數組去重。es6

let set = new Set([1, 2, 3, 1, 4]);
let arr = [...set]; console.log(arr); // [1, 2, 3, 4] 複製代碼

set 經過 add()來增長成員,將新值放在集合尾部,若是新值跟原集合中的成員重複的話,會被自動過濾掉。github

let set = new Set([1, 2, 3, 1, 4]);
set.add(1); console.log(set); // {1, 2, 3, 4} let set = new Set([1, 2, 3, 1, 4]); set.add(0); console.log(set); // {1, 2, 3, 4, 0} 複製代碼

Set 的構造函數能夠接受一個具備 Iterable 接口的其餘數據結構做爲參數。編程

// 例1
let set = new Set("string"); console.log(set); // {"s", "t", "r", "i", "n", "g"} // 例2 function foo(a, b) {  let set = new Set(arguments);  console.log(set); // {1, 2} } foo(1, 2); 複製代碼

二、set 的 API

set 的實例屬性有兩個:數組

  1. Set.prototype.constructor:構造函數,默認就是 Set 函數。
  2. Set.prototype.size: 返回 Set 對象中的值的個數。

實例方法分爲兩類,一個是操做類,一個是遍歷類。 操做類:markdown

  1. Set.prototype.add(value):在 Set 對象尾部添加一個元素。返回該 Set 對象。
  2. Set.prototype.clear():移除 Set 對象內的全部元素。
  3. Set.prototype.delete(value):移除 Set 的中與這個值相等的元素,返回 Set.prototype.has(value)在這個操做前會返回的值(即若是該元素存在,返回 true,不然返回 false)。Set.prototype.has(value)在此後會返回 false。
  4. Set.prototype.has(value):返回一個布爾值,表示該值在 Set 中存在與否。
// 例1
let set = new Set([1, 2, 3]); set.clear(); console.log(set); // {} // 例2 let set = new Set([1, 2, 3]); let result = set.delete(1); console.log(result); // true console.log(set); // {2, 3} // 例3 let set = new Set([1, 2, 3]); let result = set.has(1); console.log(result); // true 複製代碼

遍歷類:數據結構

  1. Set.prototype.values():返回一個新的迭代器對象,該對象包含 Set 對象中的按插入順序排列的全部元素的值。
  2. Set.prototype.keys():與 values()方法相同,返回一個新的迭代器對象,該對象包含 Set 對象中的按插入順序排列的全部元素的值。
  3. Set.prototype.forEach(callbackFn[, thisArg]):按照插入順序,爲 Set 對象中的每個值調用一次 callBackFn。若是提供了 thisArg 參數,回調中的 this 會是這個參數。
  4. Set.prototype.entries():返回一個新的迭代器對象,該對象包含 Set 對象中的按插入順序排列的全部元素的值的[value, value]數組。爲了使這個方法和 Map 對象保持類似,每一個值的鍵和值相等。
// 例1
let set = new Set([1, 2, 3]); let keys = set.keys(); console.log(keys); // {1, 2, 3} // 例2 let set = new Set([1, 2, 3]); let values = set.values(); console.log(values); // {1, 2, 3} 複製代碼

上述兩個例子,由於 Sset 沒有鍵名,只有值,因此 keys 和 values 的結果是同樣的。 Set 一樣擁有 forEach 方法:函數

let set = new Set([1, 2, 3]);
set.forEach((key, value) => {  console.log(`${key}:${value}`); }); // 1:1 // 2:2 // 3:3 複製代碼

跟數組的 forEach 方法不一樣的是,它的回調函數的參數是其 key 值和 value 值,可是 Set 沒有 key 值,因此其 key 和 value 相等。forEach 的第二個參數是 this 值,其綁定的是回調函數中的 this 值。 最後一個實例方法是 entries:oop

let set = new Set([1, 2, 3]);
let entries = set.entries(); console.log(entries); // { // [1, 1], // [2, 2], // [3, 3] // } 複製代碼

enteries()返回一個遍歷器,返回一個鍵值對,但其鍵值對均相等。

三、WeakSet

WeakSet 也是一個構造函數,與 Set 一直,WeakSet 和 Set 二者類似但不一樣,不一樣點主要有兩個:

  1. WeakSet 的成員值必須是對象(可迭代的對象),而並不像 set 同樣能夠是原生類型值。
  2. WeakSet 持弱引用:集合中對象的引用爲弱引用。若是沒有其餘的對 WeakSet 中對象的引用,那麼這些對象會被當成垃圾回收掉。這也意味着 WeakSet 中沒有存儲當前對象的列表。正由於這樣,WeakSet 是不可枚舉的。
let weakSet = new WeakSet([1, 2]); // Uncaught TypeError: Invalid value used in weak set
let weakSet = new WeakSet([  [1, 2],  [3, 4], ]); // {[1, 2], [3, 4]} 複製代碼

WeakSet 有三個實例方法:

  1. WeakSet.prototype.add(value):返回構造函數即 WeakSet 自己。
  2. WeakSet.prototype.delete(value):從該 WeakSet 對象中刪除 value 這個元素, 以後 WeakSet.prototype.has(value) 方法便會返回 false。
  3. WeakSet.prototype.has(value):返回一個布爾值, 表示給定的值 value 是否存在於這個 WeakSet 中。
var ws = new WeakSet();
var foo = {}; var bar = {};  ws.add(foo); ws.add(bar);  ws.has(foo); // true ws.has(bar); // true  ws.delete(foo); // 從set中刪除 foo 對象 ws.has(foo); // false, foo 對象已經被刪除了 ws.has(bar); // true, bar 依然存在 複製代碼

四、Map

在 JS 中對象是建立無序鍵 / 值對數據結構 [ 也稱爲 映射(map)] 的主要機制。可是,對象做爲映射的主要缺點是不能使用非字符串值做爲鍵。因此在 ES6 提出一個新的數據結構,Map。 Map 和對象很相似,都是鍵值對的形式,可是 Map 的鍵能夠是任意類型(對象或者原始值),NaN 也能夠做爲鍵,再也不只侷限於字符串。 任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構均可以看成 Map 構造函數的參數。

let map = new Map([[{a:1},3]]);
console.log(map);//{ { a:1 },3} 複製代碼

在 MDN 中清晰的列出了 Map 和 Object 間的不一樣:

Map Object
意外的鍵 Map 默認狀況不包含任何鍵。只包含顯式插入的鍵。 一個 Object 有一個原型, 原型鏈上的鍵名有可能和你本身在對象上的設置的鍵名產生衝突。注意: 雖然 ES5 開始能夠用 Object.create(null) 來建立一個沒有原型的對象,可是這種用法不太常見。
鍵的類型 一個 Map 的鍵能夠是任意值,包括函數、對象或任意基本類型。 一個 Object 的鍵必須是一個 String 或是 Symbol。
鍵的順序 Map 中的 key 是有序的。所以,當迭代的時候,一個 Map 對象以插入的順序返回鍵值。 一個 Object 的鍵是無序的。注意:自 ECMAScript 2015 規範以來,對象確實保留了字符串和 Symbol 鍵的建立順序; 所以,在只有字符串鍵的對象上進行迭代將按插入順序產生鍵。
Size Map 的鍵值對個數能夠輕易地經過 size 屬性獲取 Object 的鍵值對個數只能手動計算
迭代 Map 是 iterable 的,因此能夠直接被迭代。 迭代一個 Object 須要以某種方式獲取它的鍵而後才能迭代。
性能 在頻繁增刪鍵值對的場景下表現更好。 在頻繁添加和刪除鍵值對的場景下未做出優化。

五、Map 的 API

Map 的實例屬性有兩個:

  1. Map.prototype.constructor:返回一個函數,它建立了實例的原型。默認是 Map 函數。
  2. Map.prototype.size: 返回 Map 對象的鍵/值對的數量。

實例方法分爲兩類,一個是操做類,一個是遍歷類。 操做類:

  1. Map.prototype.set(key, value):設置 Map 對象中鍵的值。返回該 Map 對象。
  2. Map.prototype.get(key):返回鍵對應的值,若是不存在,則返回 undefined。
  3. Map.prototype.has(key):返回一個布爾值,表示 Map 實例是否包含鍵對應的值。
  4. Map.prototype.delete(key):若是 Map 對象中存在該元素,則移除它並返回 true;不然若是該元素不存在則返回 false。隨後調用 Map.prototype.has(key) 將返回 false 。
  5. Map.prototype.clear():移除 Map 對象的全部鍵/值對。
let map = new Map();
let key = {name:"Jack"}; let value = "啥"; map.set(key, value); //{ { name:'Jack' }:'啥'} map.get(key); // '啥' map.get(a); // undefined map.has(key); // true map.delete(key); // true map.clear(); // {} 複製代碼

遍歷類:

  1. Map.prototype.entries():返回一個新的 Iterator 對象,它按插入順序包含了 Map 對象中每一個元素的 [key, value] 數組。
  2. Map.prototype.forEach(callbackFn[, thisArg]):按插入順序,爲 Map 對象裏的每一鍵值對調用一次 callbackFn 函數。若是爲 forEach 提供了 thisArg,它將在每次回調中做爲 this 值。
  3. Map.prototype.keys():返回一個新的 Iterator 對象, 它按插入順序包含了 Map 對象中每一個元素的鍵 。
  4. Map.prototype.values():返回一個新的 Iterator 對象,它按插入順序包含了 Map 對象中每一個元素的值 。
let map = new Map([
 ["name","Jack"],  [{age:25},"啥"], ]); map.entries(); // MapIterator {"name"=>"Jack",{age:25}=>"啥"} for (let item of map.entries()) {  console.log(item); // ["name","Jack"] [{age:25},"啥"] } map.forEach((value, key, map) => {  console.log(value, key, map); // Jack name Map(2) {"name"=>"Jack",{age: 25}=>"啥"}  //"啥"{age:25} Map(2) {"name" =>"Jack",{age:25}=>"啥"} }); map.keys(); // MapIterator {"name",{age:25}} for (let item of map.keys()) {  console.log(item); // "name" {age:25} } map.values(); // MapIterator {"Jack","啥"} for (let item of map.values()) {  console.log(item); // "Jack" "啥" } 複製代碼

六、Map 和其餘數據結構的轉換

6.一、Map 轉數組

ES6 給出一個最簡單的方式,擴展運算符。

let map = new Map([
 ["name", "Jack"],  [{ age: 25 }, "啥"], ]); console.log([...map]); // [['name', 'Jack'], [{age: 25}, '啥']] 複製代碼

除此以外還能夠用 Array.from()。

let map = new Map([
 ["name", "Jack"],  [{ age: 25 }, "啥"], ]); console.log(Array.from(map)); // [['name', 'Jack'], [{age: 25}, '啥']] 複製代碼

6.二、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); // { yes: true, no: false } 複製代碼

6.3 Map 轉 JSON

Map 轉 JSON 有兩個形式,第一種是鍵名都是字符串的類型的,第二種是鍵名中包含非字符串類型。 第一種的話直接將 map 轉爲對象,而後再用 JSON.Stringfy()進行轉換。 第二種的話能夠轉成數組 JSON。

function mapToArrayJson(map) {
 return JSON.stringify([...map]); }  let myMap = new Map().set(true, 7).set({ foo: 3 }, ["abc"]); mapToArrayJson(myMap); // '[[true,7],[{"foo":3},["abc"]]]' 複製代碼

七、WeakMap

WeakMap 和 WeakSet 有些相似,都是弱引用,而且成員值的鍵必須是對象。

let weakMap = new WeakMap([[1,3]]); // Uncaught TypeError: Invalid value used as weak map key
let weakMap = new WeakMap([[{name:"Jack"},3]]); // {{name:'Jack'}:3} 複製代碼

WeakMap 有四個實例方法:

  1. WeakMap.prototype.delete(key):移除 key 的關聯對象。執行後 WeakMap.prototype.has(key)返回 false。
  2. WeakMap.prototype.get(key):返回 key 關聯對象, 或者 undefined(沒有 key 關聯對象時)。
  3. WeakMap.prototype.has(key):根據是否有 key 關聯對象返回一個 Boolean 值。
  4. WeakMap.prototype.set(key, value):在 WeakMap 中設置一組 key 關聯對象,返回這個 WeakMap 對象。
let weakMap = new WeakMap();
let obj = {name: 'Jack'}; weakMap.set(obj, 3); // {{name:'Jack'}: 3} weakMap.get(obj); // 3 weakMap.has(obj); // true weakMap.delete(obj); // true 複製代碼

八、Map的應用

Map 的應用最直接的就是策略模式。舉個例子若是一個公司的年終獎爲一等獎是電腦,二等獎是手機,三等獎是步步高點讀機,四等獎是礦泉水。那通常會這麼寫:

function getAnnualBonus(level) {
 if (level === "一等獎") {  return "電腦";  } else if (level === "二等獎") {  return "手機";  } else if (level === "三等獎") {  return "步步高點讀機";  } else if (level === "四等獎") {  return "礦泉水";  } } getAnnualBonus("一等獎"); // 或者 function getAnnualBonus(level) {  switch (level) {  case "一等獎":  return "電腦";  case "二等獎":  return "手機";  case "三等獎":  return "步步高點讀機";  case "四等獎":  return "礦泉水";  default:  break;  } } getAnnualBonus("一等獎"); 複製代碼

上述兩種寫法,若是條件愈來愈多的話,那寫的 if...else...和 case 愈來愈長,代碼至關的臃腫。 運用 Map 來寫策略模式,簡潔明瞭:

let annualBonus = new Map([
 ['一等獎', '電腦'],  ['二等獎', '手機'],  ['三等獎', '步步高點讀機'],  ['四等獎', '礦泉水'] ]); function getAnnualBonus(level) {  return annualBonus.get(level); } getAnnualBonus("一等獎"); 複製代碼

參考:

Set 和 Map 數據結構

後語

相關文章:

相關文章
相關標籤/搜索