es6入門(四)

一、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()。

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