《 ES6修煉記——Symbol 》

基礎篇

1、什麼是Symbol?

  Symbol是ES6規範中新引入的一種數據類型,表示獨一無二的值。它是JavaScript的第七種數據類型,前六種分別是:Undefined、Null、Boolean、String、Number、Object。git

2、爲何要引入Symbol類型?

  全部新技術的誕生都是爲了解決某一問題或提升生產效率,Symbol也不例外。在ES5時代,對象屬性名都是字符串,這就可能出現屬性名衝突的狀況。例如,當咱們引入了一個外部對象,若是咱們想在這個對象中添加一個新的方法,就有可能會與對象中現有的屬性產生衝突。
  想要從根本上解決這個問題,咱們須要一個永遠不會重複的數據類型,因此Symbol就應運而生了。github

3、怎樣生成一個Symbol值?

  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。 函數

不能使用new命令

4、Symbol.for( )、Symbol.keyFor( )

  除了上述方法外,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"
複製代碼

5、Symbol使用場景

(一)、用做對象屬性名

  想象一下這個場景,有一天你引入了一個外部提供的對象,你想要在這個對象里加入新的方法,可是你殊不知道你定義的屬性名已經存在於現有對象中了,這形成了一個讓你抓狂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。

1、Symbol.hasInstance

  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
複製代碼

2、Symbol.species

  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   

相關文章
相關標籤/搜索