ES6學習筆記9 Symbol、Set和Map
Symbol
ES6引入一種新的資料型別Symbol,表示獨一無二的值。Symbol值通過Symol函式生成,可傳入一個字串引數,表示對Symbol例項的描述(即使兩個Symbol描述相同,值也不同),用於區分不同的Symbol值。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
Symbol函式不能使用new命令,如果Symbol函式的引數是一個物件,則會呼叫該物件的toString方法,將其轉換為字串,再生成一個Symbol值
Symbol 值不能與其他型別的值進行運算,但可以轉換為字串和布林值
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym = Symbol(); Boolean(sym) // true !sym // false
symbol可以作為物件屬性名,這樣就保證不會與其他屬性名產生衝突。Symbol作為物件屬性名時不能使用點運算子,其必須放在方括號中。
let mySymbol = Symbol();
// 第一種寫法
let a = {};
a[mySymbol] = 'Hello!';
// 第二種寫法
let a = {
[mySymbol]: 'Hello!'
};
// 第三種寫法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上寫法都得到同樣結果
a[mySymbol] // "Hello!"
Symbol 作為屬性名,該屬性不會出現在for...in
const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
// [Symbol(a), Symbol(b)]
如果需要使用同一個Symbol值,可以通過Symbol.for方法。它接受一個字串作為引數,然後搜尋有沒有以該引數作為名稱的 Symbol 值。如果有,就返回這個 Symbol 值,否則就新建並返回一個以該字串為名稱的 Symbol 值。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
Symbol.for()與Symbol()這兩種寫法,都會生成新的 Symbol。它們的區別是,前者會被登記在全域性環境中供搜尋,而後者不會。
Symbol.keyFor()方法返回一個已登記的 Symbol 型別值的key。若Symbol不是通過Symbol.for()建立的,則返回undefined。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
Symbol.for建立 Symbol 值登記的名字,是全域性環境的,可以在不同的 iframe 或 service worker 中取到同一個值。
Set
ES6提供了set資料結構,其類似於陣列,但成員的值都是唯一的,沒有重複,有點像Python的元組。set函式可以接受一個數組(或具有iterable介面的資料結構),用來初始化,其自動去除重複的值。
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
在set結構中,NaN等於自身,且兩個物件總是不等的
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
set例項的屬性:
- Set.prototype.constructor : 建構函式
- Set.prototype.size : 返回set例項的成員總數
set例項的方法:
- add(value) : 新增某個值,返回Set結構本身
- delete(value):刪除某個值,返回一個布林值,表示刪除是否成功
- has(value):返回一個布林值,表示引數是否為set成員
- clear():清除所有成員,沒有返回值
- keys():返回鍵名的遍歷器,由於set結果沒有鍵名,故返回鍵值
- values():返回鍵值的遍歷器
- entries():返回鍵值對的遍歷器,由於set結果沒有鍵名,故返回兩個相同鍵值組成的陣列
- forEach():使用回撥函式遍歷每個成員,沒有返回值
s.add(1).add(2).add(2);
// 注意2被加入了兩次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
也可以省略values方法,直接用for...of迴圈遍歷,或者使用擴充套件運算子
for (let x of set) { console.log(x); } // red // green // blue let set = new Set(['red', 'green', 'blue']); let arr = [...set]; // ['red', 'green', 'blue']
利用擴充套件運算子,可以間接地使用陣列的map和filter方法
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set結構:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set結構:{2, 4}
通過filter方法可以實現並集(Union)、交集(Intersect)和差集(Difference)
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 並集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
如果想在遍歷過程中改變原來Set的結構,有兩種方法。第一種是利用原Set結構映射出一個新的Set,然後覆蓋原有的值。另外一種方法就是利用Array.from方法。
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
WeakSet
WeakSet 結構與 Set 類似,也是不重複的值的集合。但是,它與 Set 有兩個區別。
第一個區別是WeakSet 的成員只能是物件,而不能是其他型別的值。第二個區別是WeakSet 中的物件都是弱引用,即垃圾回收機制不考慮 WeakSet 對該物件的引用,如果其他物件都不再引用該物件,那麼垃圾回收機制會自動回收該物件所佔用的記憶體,不考慮該物件還存在於 WeakSet 之中。因此WeakSet不可遍歷,且不具有size屬性。
使用WeakSet建構函式建立例項,接受一個數組或類似組物件(具有iterable介面)作為引數,該陣列的所有成員都會成為WeakSet例項的成員。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
方法
- WeakSet.prototype.add(value) : 新增一個成員
- WeakSet.prototype.delete(value) : 刪除一個指定成員
- WeakSet.prototype.has(value) : 返回一個布林值,表示某個值是否存在於例項中
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
WeakSet 的一個用處,是儲存 DOM 節點,而不用擔心這些節點從文件移除時,會引發記憶體洩漏。Map
ES6引入Map資料結構,其與JS的物件類似,區別在於其"鍵"的範圍不限於字串,各種型別的值都可以當做鍵。而物件則是字串-值結構。
使用Map建構函式建立例項,其可以接受一個鍵值對陣列(任何具有iterable介面,成員為雙元素陣列的資料結構都可以,包括Set和Map)作為初始化引數
const map = new Map([
['name', '張三'],
['title', 'Author']
]);
map.size // 2
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
屬性
- size : 返回Map結構的成員總數
方法
- set(key, value):設定鍵名key對應的鍵值為value,返回Map,若鍵名已存在,則對應的鍵值將被更新
- get(key) : 返回key的鍵值,找不到,返回undefined
- has(key) : 查詢對應的鍵,存在返回true,否則為false
- delete(key) : 刪除某個鍵,成功返回true,否則返回false
- clear():清除所有成員
- keys():返回鍵名的遍歷器
- values():返回鍵值的遍歷器
- entries():返回鍵值對的遍歷器
- forEach():遍歷Map的所有成員
const m = new Map();
m.set('edition', 6) // 鍵是字串
m.set(262, 'standard') // 鍵是數值
m.set(undefined, 'nah') // 鍵是 undefined
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同於使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
Map轉換為其他資料結構
- Map轉化為陣列 map轉換為陣列的最便捷方法就是使用擴充套件運算子(...)
const map = new Map( [ [a,1],[b,2] ] );
[...map]
//[ [a,1],[b,2] ]
- 陣列轉換為Map 將陣列傳入Map函式就可以,轉換為Map
- Map轉換為物件
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
- 物件轉換為Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
- Map轉換為JSON
分為兩種情況,當map鍵名全是字串時,先轉為物件,再使用JSON.stringify()方法。若鍵值有非字串,則將其轉為陣列後,再使用JSON.stringify()方法。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
- JSON轉換為Map
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
WeakMap
WeakMap與map結構類似,區別在於WeakMap只接受物件(null除外)作為鍵名,且其鍵名不計入垃圾回收機制(鍵值正常),因此它沒有size屬性,也不支援各種遍歷方法,同時也沒有clear方法,只有四個方法可用:set(),get(),has(),delete()
WeakMap 應用的典型場合就是 DOM 節點作為鍵名
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener('click', function() {
let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
}, false);
上述程式碼中,每當myElement這個DOM節點被點選時,timeClicked就被更新一次,當這個DOM節點被刪除時,該狀態自動消失,不存在記憶體洩漏風險
除此之外,註冊監聽事件的物件也很可以用WeakMap實現
const listener = new WeakMap();
listener.set(element1, handler1);
listener.set(element2, handler2);
element1.addEventListener('click', listener.get(element1),false);
element2.addEventListener('click', listener.get(element2),false);
上述程式碼,一旦DOM物件消失,與它繫結的事件函式也會隨之消失