Symbol
1. 前言
在 ES5 中基礎數(shù)據(jù)類型有 5 種:Boolean
、Null
、Undefined
、Number
、String
,ES6 新增了一個基礎數(shù)據(jù)類型 Symbol
符號、代號的意思,它是獨一無二的,也就是說使用它聲明的變量是獨一無二的。引入這個數(shù)據(jù)類型有什么作用呢?
我們知道在 ES5 中, 對象的屬性名都是字符串,容易造成屬性名沖突。比如,你使用了一個他人提供的對象,但又想為這個對象添加新的方法(mixin 模式),新方法的名字就有可能與現(xiàn)有方法產(chǎn)生沖突,ES6 引入 Symbol 就可以解決這個問題。不僅如此 Symbol 的使用還有很多,在元編程中也發(fā)揮很大的作用。下面我們就來看看 Symbol 的 使用。
2. 語法詳解
2.1 基本語法
使用 Symbol()
會返回一個獨一無二的變量,可以作為對象的 key 存在,返回的值是 symbol
類型,該類型具有靜態(tài)屬性和靜態(tài)方法。
Symbol([description])
參數(shù) | 描述 |
---|---|
description | (可選)是字符串類型,對 symbol 的描述 |
2.2 基本語法
Symbol()
是一個方法,返回的值是 symbol
類型,使用如下:
var s1 = Symbol();
var s2 = Symbol();
console.log(s1 === s2) // false
console.log(typeof s1) // symbol
上面的代碼中,使用 Symbol
聲明的變量 s1 和 s2,雖然它們看上去是同一個 Symbol
定義的,但其實是不相等的。
2.3 帶描述的 Symbol
在 Symbol
中可以傳入一些參數(shù),來描述定義的 Symbol
類型的值。
var s1 = Symbol('imooc');
var s2 = Symbol('imooc');
console.log(s1) // Symbol(imooc)
console.log(s1 === s2) // false
let s = Symbol({name: 'imooc'});
console.log(s); // Symbol([object Object])
上面的代碼中,Symbol
接收的參數(shù)是一個對 Symbol
的描述,即使兩個 Symbol
接收相同值,兩個值也是不一樣的。另外,如果傳入的描述符是對象類型,內(nèi)部會將描述的內(nèi)容進行 toString
操作,所以返回的結(jié)果是 [object Object]
。
3. 作為對象的 key
Symbol
經(jīng)常會作為對象的屬性存在,如果這個屬性是用 symbol
來聲明的,則不可枚舉,也不能用 for...in
、for...of
迭代。
對象上的 key,可以用取值表達式 (中括號) 的方式取出來,作為對象上的屬性,如下:
var s = Symbol('imooc');
var obj = {
[s]: 1
}
obj // {Symbol(imooc): 1}
obj[s] // 1
上面的代碼,使用 Symbol
聲明了一個變量,然后作為對象的 key 給它賦值。取值的時候只能使用中括號的方式,因為這里的 s
是變量不能使用點的方式。下面是對 obj 對象用 for...in
進行的遍歷。
for(let key in obj) {
console.log(obj[key])
}
// undefined
上面的代碼對 obj 對象進行迭代,但是沒有打印出對應的值,說明用 Symbol
來聲明的屬性是不可枚舉的。如果想要獲取到這個屬性可以使用 Object.getOwnPropertySymbols(obj)
獲取。使用 Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
也是不能返回想要的結(jié)果。
Object.getOwnPropertySymbols(obj); // [Symbol(imooc)]
Object.keys(obj); // []
Object.getOwnPropertyNames(obj); // []
JSON.stringify(obj); // "{}"
從上面的代碼中可以看到使用 Object.getOwnPropertySymbols()
可以獲取對象上所有 Symbol
類型的屬性,并返回一個數(shù)組。
另外,可以通過 description
方法獲取 Symbol
類型描述。
var s = Symbol('imooc');
s.description; // "imooc"
4. Symbol.for () 和 Symbol.keyFor ()
Symbol.for(key)
方法也是聲明變量使用,不同的是 Symbol.for(key)
是在全局作用域下聲明的。它會根據(jù)給定的鍵 key
從運行時的 symbol
注冊表中找到對應的 symbol
。如果找到了,則返回它。否則,新建一個與該鍵關(guān)聯(lián)的 symbol
,并放入全局的 symbol
注冊表中,如果有已經(jīng)聲明了的 symbol
則不回重復聲明。
let s1 = Symbol.for('imooc');
let s2 = Symbol.for('imooc');
function fn() {
return Symbol.for('imooc');
}
console.log(s1, s2) // Symbol(imooc) Symbol(imooc)
console.log(s1 === s2) // true
console.log(fn() === s1) // true
上面的代碼中可以看出來,使用 Symbol.for(key)
無論在哪里進行聲明,都不會影響它們的值。
Symbol.keyFor()
通過 key
值獲取 symbol
的描述:
let s1 = Symbol.for('imooc');
console.log(Symbol.keyFor(s1)) // imooc
5 實戰(zhàn)案例
5.1 解決屬性重名
在現(xiàn)實中姓名重復是很常見的,但是在 JavaScript 對象中,屬性名是唯一的存在。如果定義一個對象中有重復的屬性則會被覆蓋,這個現(xiàn)象叫做 “引用消除”。我們看下面的例子:
var person = {
Tom: {sex: '男', age: 18},
David: {sex: '男', age: 17},
David: {sex: '女', age: 16},
}
console.log(person); // {Tom: {sex: "男", age: 18}, David: {sex: "女", age: 16}}
可以看到我們定義了一個 person 對象,第一個 David 對象被后一個 David 引用消除了,所以只有第二個 David 的數(shù)據(jù)。如果要解決這個問題可以使用 Symbol
來實現(xiàn)
var person = {
Tom: {sex: '男', age: 18},
[Symbol('David')]: {sex: '男', age: 17},
[Symbol('David')]: {sex: '女', age: 16},
}
console.log(person)
// {Tom: {sex: "男", age: 18}, Symbol(David): {sex: "男", age: 17}, Symbol(David): {sex: "女", age: 16}}
這樣就可以解決屬性名沖突的問題,需要注意的是使用這樣的方式定義對象數(shù)據(jù)存在一個問題,就是使用 for...in
或者使用 Object.keys()
遍歷時 Symbol 屬性的數(shù)據(jù)不會被遍歷到,上文有具體說明。所以,如果想要遍歷到對象的值可以通過 Reflect.ownKeys()
去獲取對象的 key,然后進行循環(huán)操作。
for (let key of Reflect.ownKeys(person)) {
console.log(person[key])
}
// {sex: "男", age: 18}
// {sex: "男", age: 17}
// {sex: "女", age: 16}
5.2 消除魔術(shù)字符串
魔術(shù)字符串 指的是在代碼中多次出現(xiàn)與代碼形成強耦合的某一個具體的字符串或者數(shù)值,看下面的例子:
function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Circle':
area = 3.14 * Math.pow(options.radius, 2)
break;
case 'Square':
area = options.width * options.height;
break;
}
return area;
}
getArea('Circle', { radius: 10 }); // 314
getArea('Square', { width: 10, height: 10 }); // 100
上面的代碼中 ‘Circle’ 和 ‘Triangle’ 就屬于魔術(shù)字符串,常見的消除魔術(shù)字符串的方法就是使用變量替代,如下:
const shapeType = {
circle: 'Circle',
triangle: 'Square'
}
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.circle:
area = 3.14 * Math.pow(options.radius, 2)
break;
case shapeType.square:
area = options.width * options.height;
break;
}
return area;
}
getArea(shapeType.circle, { radius: 10 }); // 314
getArea(shapeType.square, { width: 10, height: 10 }); // 100
上面的代碼中就消除了代碼的強耦合,其實我們不關(guān)注 shapeType 屬性的值,只要他們不同即可,有了 Symbol 這時我們就可以使用 Symbol 進行描述,如下更改:
const shapeType = {
circle: Symbol('Circle'),
triangle: Symbol('Square')
}
6. 小結(jié)
本節(jié)學習了 ES6 新增數(shù)據(jù)類型 Symbol
,使用它可以聲明一個獨一無二的變量,通常會作為對象的屬性存在,解決屬性名沖突的問題。注意這個屬性是不能被迭代的,如果想要迭代它可以使用 Reflect.ownKeys()
的方式去獲取 key 值。最后介紹了 Symbol
在實戰(zhàn)中的應用。