ES6學(xué)習(xí)筆記7-symbol

概述

ES5 的對(duì)象屬性名都是字符串,這容易造成屬性名的沖突。比如,你使用了一個(gè)他人提供的對(duì)象,但又想為這個(gè)對(duì)象添加新的方法(mixin 模式),新方法的名字就有可能與現(xiàn)有方法產(chǎn)生沖突。如果有一種機(jī)制,保證每個(gè)屬性的名字都是獨(dú)一無二的就好了,這樣就從根本上防止屬性名的沖突。這就是 ES6 引入Symbol的原因。

Symbol 值通過Symbol函數(shù)生成。這就是說,對(duì)象的屬性名現(xiàn)在可以有兩種類型,一種是原來就有的字符串,另一種就是新增的 Symbol 類型。凡是屬性名屬于 Symbol 類型,就都是獨(dú)一無二的,可以保證不會(huì)與其他屬性名產(chǎn)生沖突。

注意,Symbol函數(shù)前不能使用new命令,否則會(huì)報(bào)錯(cuò)。這是因?yàn)樯傻?Symbol 是一個(gè)原始類型的值,不是對(duì)象。也就是說,由于 Symbol 值不是對(duì)象,所以不能添加屬性?;旧?,它是一種類似于字符串的數(shù)據(jù)類型。

Symbol函數(shù)可以接受一個(gè)字符串作為參數(shù),表示對(duì) Symbol 實(shí)例的描述,主要是為了在控制臺(tái)顯示,或者轉(zhuǎn)為字符串時(shí),比較容易區(qū)分。

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

如果 Symbol 的參數(shù)是一個(gè)對(duì)象,就會(huì)調(diào)用該對(duì)象的toString方法,將其轉(zhuǎn)為字符串,然后才生成一個(gè) Symbol 值。

注意,Symbol函數(shù)的參數(shù)只是表示對(duì)當(dāng)前 Symbol 值的描述,因此相同參數(shù)的Symbol函數(shù)的返回值是不相等的。

Symbol 值不能與其他類型的值進(jìn)行運(yùn)算,會(huì)報(bào)錯(cuò)。但是,Symbol 值可以顯式轉(zhuǎn)為字符串。另外,Symbol 值也可以轉(zhuǎn)為布爾值,但是不能轉(zhuǎn)為數(shù)值。

作為屬性名的 Symbol

由于每一個(gè) Symbol 值都是不相等的,這意味著 Symbol 值可以作為標(biāo)識(shí)符,用于對(duì)象的屬性名,就能保證不會(huì)出現(xiàn)同名的屬性。這對(duì)于一個(gè)對(duì)象由多個(gè)模塊構(gòu)成的情況非常有用,能防止某一個(gè)鍵被不小心改寫或覆蓋。

注意,Symbol 值作為對(duì)象屬性名時(shí),不能用點(diǎn)運(yùn)算符。

const mySymbol = Symbol();
const a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

上面代碼中,因?yàn)辄c(diǎn)運(yùn)算符后面總是字符串,所以不會(huì)讀取mySymbol作為標(biāo)識(shí)名所指代的那個(gè)值,導(dǎo)致a的屬性名實(shí)際上是一個(gè)字符串,而不是一個(gè) Symbol 值。

Symbol 類型還可以用于定義一組常量,保證這組常量的值都是不相等的。

還有一點(diǎn)需要注意,Symbol 值作為屬性名時(shí),該屬性還是公開屬性,不是私有屬性。

實(shí)例:消除魔術(shù)字符串

魔術(shù)字符串指的是,在代碼之中多次出現(xiàn)、與代碼形成強(qiáng)耦合的某一個(gè)具體的字符串或者數(shù)值。風(fēng)格良好的代碼,應(yīng)該盡量消除魔術(shù)字符串,改由含義清晰的變量代替。

function getArea(shape, options) {
  let area = 0;

  switch (shape) {
    case 'Triangle': // 魔術(shù)字符串
      area = .5 * options.width * options.height;
      break;
    /* ... more code ... */
  }

  return area;
}

getArea('Triangle', { width: 100, height: 100 }); // 魔術(shù)字符串

上面代碼中,字符串Triangle就是一個(gè)魔術(shù)字符串。它多次出現(xiàn),與代碼形成“強(qiáng)耦合”,不利于將來的修改和維護(hù)。

常用的消除魔術(shù)字符串的方法,就是把它寫成一個(gè)變量。

const shapeType = {
  triangle: 'Triangle'
};

function getArea(shape, options) {
  let area = 0;
  switch (shape) {
    case shapeType.triangle:
      area = .5 * options.width * options.height;
      break;
  }
  return area;
}

getArea(shapeType.triangle, { width: 100, height: 100 });

上面代碼中,我們把Triangle寫成shapeType對(duì)象的triangle屬性,這樣就消除了強(qiáng)耦合。

如果仔細(xì)分析,可以發(fā)現(xiàn)shapeType.triangle等于哪個(gè)值并不重要,只要確保不會(huì)跟其他shapeType屬性的值沖突即可。因此,這里就很適合改用 Symbol 值。

const shapeType = {
  triangle: Symbol()
};

屬性名的遍歷

Symbol 作為屬性名,該屬性不會(huì)出現(xiàn)在for...in、for...of循環(huán)中,也不會(huì)被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有屬性,有一個(gè)Object.getOwnPropertySymbols方法,可以獲取指定對(duì)象的所有 Symbol 屬性名。

另一個(gè)新的 API,Reflect.ownKeys方法可以返回所有類型的鍵名,包括常規(guī)鍵名和 Symbol 鍵名。

由于以 Symbol 值作為名稱的屬性,不會(huì)被常規(guī)方法遍歷得到。我們可以利用這個(gè)特性,為對(duì)象定義一些非私有的、但又希望只用于內(nèi)部的方法。
let size = Symbol('size');
如一個(gè)數(shù)組的大小。

Symbol.for(),Symbol.keyFor()

有時(shí),我們希望重新使用同一個(gè) Symbol 值,Symbol.for方法可以做到這一點(diǎn)。它接受一個(gè)字符串作為參數(shù),然后搜索有沒有以該參數(shù)作為名稱的 Symbol 值。如果有,就返回這個(gè) Symbol 值,否則就新建并返回一個(gè)以該字符串為名稱的 Symbol 值。

比如,如果你調(diào)用Symbol.for("cat")30 次,每次都會(huì)返回同一個(gè) Symbol 值,但是調(diào)用Symbol("cat")30 次,會(huì)返回 30 個(gè)不同的 Symbol 值。

Symbol.keyFor方法返回一個(gè)已登記的 Symbol 類型值的key。
需要注意的是,Symbol.for為 Symbol 值登記的名字,是全局環(huán)境的,可以在不同的 iframe 或 service worker 中取到同一個(gè)值。

實(shí)例:模塊的 Singleton 模式

Singleton 模式指的是調(diào)用一個(gè)類,任何時(shí)候返回的都是同一個(gè)實(shí)例。

對(duì)于 Node 來說,模塊文件可以看成是一個(gè)類。怎么保證每次執(zhí)行這個(gè)模塊文件,返回的都是同一個(gè)實(shí)例呢?

很容易想到,可以把實(shí)例放到頂層對(duì)象global。

// mod.js
function A() {
  this.foo = 'hello';
}

if (!global._foo) {
  global._foo = new A();
}

module.exports = global._foo;

然后,加載上面的mod.js。

const a = require('./mod.js');
console.log(a.foo);

上面代碼中,變量a任何時(shí)候加載的都是A的同一個(gè)實(shí)例。

但是,這里有一個(gè)問題,全局變量global._foo是可寫的,任何文件都可以修改。
為了防止這種情況出現(xiàn),我們就可以使用 Symbol。

// mod.js
const FOO_KEY = Symbol.for('foo');

function A() {
  this.foo = 'hello';
}

if (!global[FOO_KEY]) {
  global[FOO_KEY] = new A();
}

module.exports = global[FOO_KEY];

但由上面的代碼可知,這個(gè)東西Symbol.for('foo');是全局的,我們?cè)谄渌胤絝or一下也可以得到這個(gè)key值,還是可以修改,所以我們可以直接用非for的Symbol方法,但是這樣也不絕對(duì)安全可靠,因?yàn)槊看螆?zhí)行腳本就會(huì)生成新的key....

內(nèi)置的 Symbol 值

除了定義自己使用的 Symbol 值以外,ES6 還提供了 11 個(gè)內(nèi)置的 Symbol 值,指向語言內(nèi)部使用的方法。

  • Symbol.hasInstance
    其他對(duì)象使用instanceof運(yùn)算符,判斷是否為該對(duì)象的實(shí)例時(shí),會(huì)調(diào)用這個(gè)方法。比如,foo instanceof Foo在語言內(nèi)部,實(shí)際調(diào)用的是FooSymbol.hasInstance。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容