1. 程式人生 > >ES6學習筆記9 Symbol、Set和Map

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

for...of迴圈中,也不會被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回,但可以通過Object.getOwnPropertySymbols()方法,來獲取指定物件的所有Symbol屬性名。

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']

利用擴充套件運算子,可以間接地使用陣列的mapfilter方法

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轉換為物件
主要思路就是新建一個物件,複製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
由於鍵名是字串,直接呼叫JSON.parse()方法,再轉為map。當為JSON陣列時,先呼叫JSON.parse()方法,再將其傳入map建構函式即可
function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}
function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

WeakMap

WeakMapmap結構類似,區別在於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物件消失,與它繫結的事件函式也會隨之消失