Symbol是ES6規範中新引入的一種數據類型,表示獨一無二的值。它是JavaScript的第七種數據類型,前六種分別是:Undefined、Null、Boolean、String、Number、Object。git
全部新技術的誕生都是爲了解決某一問題或提升生產效率,Symbol也不例外。在ES5時代,對象屬性名都是字符串,這就可能出現屬性名衝突的狀況。例如,當咱們引入了一個外部對象,若是咱們想在這個對象中添加一個新的方法,就有可能會與對象中現有的屬性產生衝突。
想要從根本上解決這個問題,咱們須要一個永遠不會重複的數據類型,因此Symbol就應運而生了。github
Symbol值能夠直接調用Symbol函數生成(還有另一種生成方法,後面會講到),每個Symbol值都是獨一無二的,使用typeof檢測實例的類型爲symbol。數組
let a = Symbol() let b = Symbol() a === b // false typeof a // symbol 複製代碼
Symbol函數也能夠接收一個字符串參數,做爲對生成的Symbol描述,其主要目的是爲在控制檯顯示時容易區分不一樣的Symbol值。請注意,不一樣的Symbol值可使用相同的描述,可是不表明他們是相等的。bash
let a = Symbol() let b = Symbol('symbol-describe') let c = Symbol('symbol-describe') a // Symbol() -->若是不加描述,那麼再控制檯中輸出的都是Symbol() b // Symbol(symbol-describe) c // Symbol(symbol-describe) b === c // false 複製代碼
Symbol值能夠顯式轉爲字符串,但不能隱式轉換成字符串,也不能和其餘任何類型的值進行運算,不然代碼會報錯!此外,Symbol能夠轉換爲布爾值,但不能轉爲數值。markdown
let sym = Symbol('symbol') String(sym) // "Symbol(symbol)" sym.toString() // "Symbol(symbol)" 'this is' + sym // Cannot convert a Symbol value to a string `this is ${sym}` // Cannot convert a Symbol value to a string Boolean(sym) // true !sym // false if(sym){ // true } Number(sym) // TypeError 複製代碼
須要注意的是:
不能使用new命令來調用Symbol函數,不然代碼會報錯!由於生成的Symbol值是一個原始類型的值,而不是對象。此外,由於Symbol值是原始類型,因此用instanceof判斷Symbol值會直接返回false。 函數
除了上述方法外,Symbol.for()方法也能夠生成一個Symbol值。Symbol.for()接收一個key值(字符串)做爲參數。在運行時,會先在全局環境下搜索,是否有用該key值建立過Symbol實例。若是存在,則不會再新建立一個Symbol值,而是直接返回這個Symbol值。若是不存在,纔會建立一個新的Symbol值,並在全局環境中登記,供下次搜索。 oop
let a = Symbol.for('my-symbol') let b = Symbol.for('my-symbol') a === b // true --> 由於已經存在以'my-symbol'爲key值建立的實例,因此在建立b時直接返回了a let c = Symbol('symbol') let d = Symbol.for('symbol') c === d // false --> Symbol('symbol')方法並不會登記,因此Symbol.for會新建立一個值 複製代碼
與Symbol.for方法對應的還有一個Symbol.keyFor方法,此方法能夠獲取一個已登記Symbol值的key值。若是Symbol值沒有登記,則返回undefined。 this
let sym1 = Symbol() let sym2 = Symbol.for('mySymbol') Symbol.keyFor(sym1) // undefined Symbol.keyFor(sym2) // "mySymbol" 複製代碼
想象一下這個場景,有一天你引入了一個外部提供的對象,你想要在這個對象里加入新的方法,可是你殊不知道你定義的屬性名已經存在於現有對象中了,這形成了一個讓你抓狂bug。。。此時,若是你使用了Symbol做爲屬性名,就徹底不用擔憂屬性名會衝突,由於每個Symbol都是獨一無二的。在對象中使用Symbol屬性名有如下三種寫法: spa
let sym = Symbol() // 寫法一 let a = {} a[sym] = 'juejin' // 寫法二 let a = { "sym" : 'juejin', [sym] : 'juejin', // 你能夠隨意使用本身想用的名字,不會產生衝突 [sym2](){...} // 也能夠採用簡潔的寫法 } // 寫法三 Object.defineProperty(a, sym, {value:'juejin'}) 複製代碼
須要注意的是:
1. 不能使用點運算符去定義Symbol類型的屬性名,由於點運算符始會把後面的值讀取爲字符串。code
let sym = Symbol() let obj = {} obj.sym = 'juejin' obj[sym] // undefined obj['sym'] //'juejin' 複製代碼
2. 用for...in、for...of、Object.keys()、Object.getOwnPropertyNames()方法遍歷對象時,均不會遍歷出Symbol屬性名。有兩個方法能夠獲取Symbol類型的屬性名:
Object.getOwnPropertySymbols,會返回一個數組,包含當前對象全部用做屬性名的Symbol值,不返回字符串屬性名。
let obj = {} let sym = Symbol() obj.a = 'juejin' obj[sym] = 'a symbol' Object.getOwnPropertySymbols(obj) // [Symbol()] 複製代碼
Reflect.ownKeys,會返回一個數組,包含全部類型的鍵名
let obj = { num:1, [Symbol('sym')]:'juejin' } Reflect.ownKeys(obj) // ["num", Symbol(sym)] 複製代碼
由於Symbol值做爲對象屬性名時,不會被常規方法遍歷到,因此,咱們能夠利用這個特性來定義一些非私有的內部方法。
var size = Symbol('size') class Collection{ constructor(){ this[size] = 0 } add(item){ this[this[size]] = item this[size]++ } static sizeOf(instance){ return instance[size] } } var x = new Collection() Collection.sizeOf(x) // 0 x.add('foo') Collection.sizeOf(x) // 1 Object.keys(x) // ['0'] ,由於size屬性名是Symbol值,因此沒法獲取 Object.getOwnPropertyNames(x) // ['0'] ,由於size屬性名是Symbol值,因此沒法獲取 Object.getOwnPropertySymbols(x) // [Symbol(size)] 複製代碼
假若有一個計算面積的函數,能夠根據傳入的參數,採用不一樣的計算公式,以下:
var shapeType = { triangle: 'Triangle',square: 'Square',...}; function getArea(shape, options) { var area = 0; switch (shape) { case shapeType.triangle: area = .5 * options.width * options.height; break; case shapeType.square: area = options.width * options.height; break; ... } return area; } getArea(shapeType.triangle, { width: 100, height: 100 }); 複製代碼
仔細觀察能夠發現,shapeType對象裏的鍵值是什麼並不重要,咱們只要保證他們不同就能夠了,這種狀況就很是適合採用Symbol。
var shapeType = { triangle: Symbol(), square: Symbol(),...}; 複製代碼
因此,當某個場景咱們僅僅須要不相同的變量時,而不在意變量的值是什麼,這時候咱們就能夠用Symbol。
運用上述的知識,咱們能夠定義本身的Symbol值。除此以外,ES6還提供了11個內置的Symbol值,這些值其實與上述的Symbol知識點關係不大,咱們能夠把他們理解爲JavaScript中特定的值,他們表明着某個方法或某個值。我挑選其中兩個工做中經常使用的介紹下,其餘的能夠參考MDN。
Symbol.hasInstance是對象的一個屬性,它指向一個方法,對象使用instanceof運算符時會調用這個方法。也就是說,當咱們使用arr instanceof Array時,在語言內部其實是調用了Array[Symbol.hasInstance] (arr) 這個方法,來去檢測arr是不是Array的實例。既然instanceof的本質是Symbol.hasInstance方法,那麼假如咱們改寫一個構造函數的Symbol.hasInstance方法,就能夠改變instanceof的行爲:
class Test{ static [Symbol.hasInstance](param){ return param === 'juejin' } } 'github' instanceof Test // false 'juejin' instanceof Test // true 複製代碼
Symbol.species是對象的屬性,它指向當前對象的構造函數。當建立衍生對象時,會調用這個方法。也就是說,把調用這個函數後返回的函數當作構造函數,來建立衍生對象。默認的Symbol.species屬性,會返回當前構造函數自己。請看下面這個例子:
class MyArray extends Array { // 默認的Symbol.species方法等同於下面的寫法,Symbol.species採用了get讀取器,避免被修改 static get [Symbol.species](){ // retrun this } } const a = new MyArray(1, 2, 3); const b = a.map(x => x); const c = a.filter(x => x > 1); // 在建立b和c時,其實是使用了MyArray內部的Symbol.species方法返回的this當作構造函數,創造了b和c b instanceof MyArray // true c instanceof MyArray // true 複製代碼
咱們能夠覆蓋對象原有的Symbol.species屬性,這樣能夠改變一些原有的行爲,產生特殊的效果:
class myArray extends Array{ static get [Symbol.species](){ return Array // 把構造函數改寫成Array } } var arr = new myArray(1,2,3) var mapArr = arr.map(x => x); // 建立衍生對象mapArr mapArr instanceof MyArray // false,由於Symbol.species已經被修改,因此mapArr不是MyArray的實例 mapArr instanceof Array // true 複製代碼
參考資料:
《ES6標準入門》_阮一峯
Symbol — MDN