Symbol
特性
- 惟一性
- 屬性名屬於
Symbol
類型的,都是獨一無二的,能夠保證不會與其餘屬性名產生衝突。即便是兩個聲明徹底同樣的也是不相等的。
// 沒有參數的狀況
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有參數的狀況
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
複製代碼
- 不能和其餘類型運算
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
複製代碼
- 類型轉換
Symbol
值能夠顯式轉爲字符串和布爾值,可是不能轉爲數值。
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym = Symbol();
Boolean(sym) // true
!sym // false
Number(sym) // TypeError
sym + 2 // TypeError
複製代碼
Symbol的應用
- 常見的惟一值
Symbol
自己的特性就是任何兩個Symbol
類型的值都不相等,因此咱們能夠在不知到命名變量時,都設置爲Symbol
類型。Symbol
類型不能使用new操做符,由於生成的 Symbol
是一個原始類型的值,不是對象。
- 私有屬性
- 因爲
Symbol
類型的做爲屬性名時,遍歷對象的時候,該屬性不會出如今for...in
、for...of
循環中,也不會被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。須要經過Object.getOwnPropertySymbols()
方法,能夠獲取指定對象的全部 Symbol
屬性名。這樣就能夠把Symbol
類型的屬性做爲私有屬性。
- 新的
API
,Reflect.ownKeys()
方法能夠返回全部類型的鍵名,包括常規鍵名和 Symbol
鍵名。
- 消除魔法字符串
- 魔術字符串指的是,在代碼之中屢次出現、與代碼造成強耦合的某一個具體的字符串或者數值。咱們能夠把對應的字符串或數值設置成Symbol類型便可。
const name = {
first: Symbol('detanx')
}
function getName(firstName) {
switch (firstName) {
case name.first: // 魔術字符串
...
}
}
getName(name.first); // 魔術字符串
複製代碼
Symbol.for()和Symbol.keyFor()
Symbol.for()
不會每次調用就返回一個新的 Symbol
類型的值,而是會先檢查給定的key是否已經存在,若是不存在纔會新建一個值。即若是傳入相同的key
建立的值是相等的。而Symbol()
每次建立的值都不相同,即便key
相同。
let s1 = Symbol.for('detanx');
let s2 = Symbol.for('detanx');
s1 === s2 // true
let s1 = Symbol('detanx');
let s2 = Symbol('detanx');
s1 === s2 // false
複製代碼
Symbol.keyFor()
方法返回一個已登記的 Symbol
類型值的key
。未登記的 Symbol
值,返回undefined
。
let s1 = Symbol.for("detanx");
Symbol.keyFor(s1) // "detanx"
let s2 = Symbol("detanx");
Symbol.keyFor(s2) // undefined
複製代碼
Symbol()
寫法沒有登記機制,因此每次調用都會返回一個不一樣的值。
Symbol.for()
爲 Symbol
值登記的名字,是全局環境的,無論有沒有在全局環境運行。
內置的Symbol值
Set 和 Map 數據結構
Set
- 特性
- 相似於數組,可是成員的值都是惟一的,沒有重複的值。
Set
函數能夠接受一個數組(或者具備 iterable
接口的其餘數據結構)做爲參數,用來初始化。
- 內部兩個數據是否相同基本和
===
相似,區別是在Set
中NaN
和NaN
也是相等的。
- 應用
[...new Set([1, 1, 2, 3])] // [1, 2, 3]
[...new Set('ababbc')].join('') // "abc"
複製代碼
Set
實例的屬性和方法
Set.prototype.constructor:構造函數,默認就是Set函數。
Set.prototype.size:返回Set實例的成員總數。
複製代碼
Set.prototype.add(value):添加某個值,返回 Set 結構自己。
Set.prototype.delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
Set.prototype.has(value):返回一個布爾值,表示該值是否爲Set的成員。
Set.prototype.clear():清除全部成員,沒有返回值。
複製代碼
Set.prototype.keys():返回鍵名的遍歷器
Set.prototype.values():返回鍵值的遍歷器
Set.prototype.entries():返回鍵值對的遍歷器
Set.prototype.forEach():使用回調函數遍歷每一個成員
複製代碼
WeakSet
- 與
Set
的區別
WeakSet
的成員只能是對象(null
除外),而不能是其餘類型的值。
const ws = new WeakSet();
ws.add(1) // TypeError: Invalid value used in weak set
ws.add(Symbol()) // TypeError: invalid value used in weak set
ws.add(null) // TypeError: invalid value used in weak set
複製代碼
WeakSet
中的對象都是弱引用,即垃圾回收機制不考慮WeakSet
對該對象的引用,也就是說,若是其餘對象都再也不引用該對象,那麼垃圾回收機制會自動回收該對象所佔用的內存,不考慮該對象還存在於 WeakSet
之中。
- 方法。
WeakSet.prototype.add(value):向 WeakSet 實例添加一個新成員。
WeakSet.prototype.delete(value):清除 WeakSet 實例的指定成員。
WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在 WeakSet 實例之中。
複製代碼
WeakSet
沒有size
屬性,沒有辦法遍歷它的成員。
Map
- 因爲傳統的
Object
對象只能使用字符串看成鍵,因此新增的Map
結構能夠將各類類型的值(包括對象)均可以看成鍵。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
複製代碼
- 方法
- 相比
Set
的操做方法,Map
沒有add
方法,新增了get
方法和set
方法。遍歷方法則是基本是同樣的。
Map.prototype.get(key) // 讀取key對應的鍵值,若是找不到key,返回undefined。
Map.prototype.has(key) // 返回一個布爾值,表示某個鍵是否在當前 Map 對象之中。
複製代碼
- 轉換
(1) Map
和數組
- 在第二彈中也提到了
Map
和數組之間轉換,他們之間互轉是很方便的。
// Map轉爲數組
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
// 數組轉爲 Map。
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
複製代碼
(2) Map
和對象
Map
的鍵都是字符串,它能夠無損地轉爲對象。若是有非字符串的鍵名,那麼這個鍵名會被轉成字符串,再做爲對象的鍵名。對象轉爲 Map
能夠經過Object.entries()
。
// Map => Object
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 }
// Object => Map
// let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
複製代碼
- 應用
- 在存儲的數據是鍵值對的時候,而且鍵名的類型多是多種類型是可使用
Map
結構。與java
中的Map
結構有區別。
WeakMap
- 與
Map
的區別
WeakMap
只接受對象做爲鍵名(null
除外),不接受其餘類型的值做爲鍵名。
const map = new WeakMap();
map.set(1, 2) // TypeError: 1 is not an object!
map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key
map.set(null, 2) // TypeError: Invalid value used as weak map key
map.set(new Number(1), 2) // WeakMap {Number => 2}
複製代碼
WeakMap
的鍵名所指向的對象,不計入垃圾回收機制。一旦再也不須要這兩個對象,咱們就必須手動刪除這個引用,不然垃圾回收機制就不會釋放佔用的內存。
const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
[e1, 'foo 元素'],
[e2, 'bar 元素'],
];
// 不須要 e1 和 e2 的時候
// 必須手動刪除引用
arr [0] = null;
arr [1] = null;
複製代碼
- WeakMap 弱引用的只是鍵名,而不是鍵值。鍵值依然是正常引用。
- 沒有遍歷操做(即沒有
keys()
、values()
和entries()
方法),也沒有size
屬性。
- 沒法清空,即不支持
clear
方法。WeakMap
只有四個方法可用:get()
、set()
、has()
、delete()
。
- 應用
(1) DOM
節點做爲鍵名
document.getElementById('logo')
是一個DOM
節點,每當發生click
事件,就更新一下狀態。咱們將這個狀態做爲鍵值放在 WeakMap
裏,對應的鍵名就是這個節點對象。一旦這個 DOM
節點刪除,該狀態就會自動消失,不存在內存泄漏風險。
let myWeakmap = new WeakMap();
myWeakmap.set(
document.getElementById('logo'),
{timesClicked: 0})
;
document.getElementById('logo').addEventListener('click', function() {
let logoData = myWeakmap.get(document.getElementById('logo'));
logoData.timesClicked++;
}, false);
複製代碼
(2) 部署私有屬性
- 內部屬性是實例的弱引用,因此若是刪除實例,它們也就隨之消失,不會形成內存泄漏。
const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
複製代碼