Javascript Symbol 隱匿的將來之星

ES6中基礎類型增長到了7種,比上一個版本多了一個Symbol,貌似出現了很長時間,但卻因沒有使用場景,一直看成一個概念層來理解它,我想,用它的最好的方式,仍是要主動的去深刻了解它吧,因此我從基礎部分和總結的實用場景來分析這個特性。已經瞭解使用方法或者時間緊迫者能夠從實用場景一節開始閱讀javascript

base

首先,它給個人第一感受就是ES6作出了不少釋放語言特性方面的改變,它能讓咱們更加了解語言內部機制,Symbol以對象的鍵值定義,好比java

let key = Symbol('test');  
 let obj = {};
 obj[key] = 'alone';
 obj[key];  // "alone"

Symbol正如其名,表示一個惟一的標示,以屬性的方式存在於對象當中,它接收一個參數,沒有實質的做用,只是爲了作一個描述。以上咱們經過直接量的方式來定義它,而且取值時,也須要使用key進行讀取,若是出現跨做用域的狀況,是否是就不能獲取了?git

function sent(key){
    accept({[key]:"2018"})
}
function accept(obj) {
   obj[???] //我怎麼拌?
}

以上兩個做用域中,若是不把key傳遞過來,是沒法讀取的,一個屬性還好,可是若是多了,那麼靠參數傳遞key是不現實的. 在這種狀況下,咱們可使用 Symbol.for 來爲它再添加一個標示,它接受一個參數String{key}。一般,它作爲一個偏功能性的標記來表示,在全劇中它是惟一的。es6

function sent(key){
    return accept({[key]:"2018"},key)
}
function accept(obj,key) {
   console.log(Symbol.keyFor(key))  //CURRENT_YEAR
   return obj[Symbol.for(Symbol.keyFor(key))] //CURRENT_YEAR
}
sent(Symbol.for('CURRENT_YEAR'))

而且使用 Symbol.for 來生成,會在存入當前全局上下文中一個<List>結構中,咱們稱它爲GlobalSymbolRegistry , 顧名思義,它是全局的,因此使用key時咱們須要謹慎,尤爲是在大型項目中。github

須要還注意如下幾點:segmentfault

  1. 讀取它須要使用 getOwnPropertySymbols 方法,具體請參看MDN
  2. Symbol() !== Symbol() but Symbol.for('t') === Symbol.for('t')
  3. GlobalSymbolRegistry對象存在於當前窗口進程中,直到關閉窗口,才清除掉
目前的瀏覽器版本中把Symbol打印出來是字符串的格式,並無顯示具體的對象結構,咱們能夠直接打印 Symbol,來查看對應的prototype屬性以及內部方法,因此 Symbol().__proto__ === Symbol.prototype

在使用 Symbol 作key值時,它經歷瞭如下步驟數組

  1. 若是指向對象是沒有定義的則拋出類型錯誤
  2. 若是描述符爲undefined則爲''
  3. 把描述符轉換爲String格式
  4. 生成惟一的key,並返回
  5. 最後一步,把這個key賦給對象,並以Symbol(des)的方式顯示,其內部仍是以key爲準,因此 Symbol() !== Symbol() ,即使他們看起來都是 字符串的"Symbol()"

因此這樣寫也是能夠的,可是貌似沒有什麼意義瀏覽器

var n = 1;
var key = Symbol('numer')
n[key] = ‘Symbol Number’

n[key]的時候把n隱式轉換成封裝對象,併爲他添加Symbol,但並無辦法去經過封裝對象回訪這個Symbolasync

除了單純的用key之外,在Symbol類下還有一些有意思的方法,following :函數

iterator

爲指向對象添加 iterator 接口,好比使用數組解構或者使用for of,它接受一個generator函數

class IteratorExec {
    constructor(){ this.count = 1 }
    *[Symbol.iterator] = function* (){
        yield this.count++;
        yield this.count++;
        yield this.count++;
    }
 }
let obj = new IteratorExec()
[...obj] //[1,2,3]

經過添加iterator使用數據解構,還可使用for of

let values = [];
 for (let value of obj) { values.push(value) }
 values;  //[1,2,3]

注:ES6中Map,Set,數組和添加了Iterator接口的對象,擁有Iterator接口.

asyncIterator

這不是ES6中的特性,貌似放到了ES7中,能夠提早意淫一下以下代碼:

for await (const line of readLines(filePath)) {
  console.log(line);
}

toPrimitive

在對對象類型進行轉換時,會進行一次 toPrimitive,利用這個Symbol能夠改變目標對象的轉換規則,改變了之前的 "[object Object]"的固定形式

let obj = {
    [Symbol.toPrimitive](hint){
      switch(hint){
          case 'number': return 5;
          case 'string': return 'string';
          case 'default': return 'default'    
      }
    }
}
obj+11 // 'default11'
obj*2 // 10

這裏須要注意+ Number操做是不屬於 'number' 的,其餘正常,這樣就能夠定義轉對象類型的轉換規則了。

toStringTag

在javascript一切皆爲對象,而在每一個對象中,都會有一個內部屬性[[Class]]表示其對象類型,這在Symbol.toStringTag,中是能夠修改的,也就是說 '[object Object]' 後邊的字符串是可自定義的

let obj = {
    [Symbol.toStringTag]:'custom'
}
Object.prototype.toString(obj); // [object Object]
obj.toString();  //[object custom]

一般咱們使用Object.prototype.toString讀取對象屬性,正是由於向後兼容,規範在對象自身的toString上實現了這種特性,而老式方法依舊使用。可是咱們可使用如下方式:

obj = {
    [Symbol.toStringTag]:'custom'
    get [Symbol.toStringTag](){
        return 'custom'
    }
}
Object.prototype.toString.call(obj)

咱們把obj傳入執行toString,能夠達到這種效果,能夠預想es6中,Object.toString是受到上下文的影響的. 顯然,咱們上面的兩個例子都是獲取的Object.prototype.toString 二者有很大區別,只有它才能準確轉換,若是你的toString不全等於它,那是沒法轉換的,好比

var n = new Number();
 n[Symbol.toStringTag] = 123;
 n.toString();  // 「0」

太幼稚了,太無聊了?,Number私有的toString是直接把[[PrimitiveValue]]轉換成了字符串,這裏你們要千萬留心,不要誤認爲全部的對象添加了Symbol.toStringTag均可以改變,若是當前對象不是純對象,那麼你能夠爲此對象添加一個 getter 返回對應的類型,這樣外部在使用Object...call的時,會獲取自定的類型。因此,這須要外部配合使用,你添加getter,人家不call你也是沒辦法的。

另外Symbol暴露了幾種爲原生對象定義了一些類型,好比

Math.toString();  //[object Math]

其餘類型有 JSON, Promise, Map, TypedArray, DataView, ArrayBuffer, Genterator等等

unscopeables

const object1 = {
  property1: 42
};

object1[Symbol.unscopables] = {
  property1: true
};

with (object1) {
  console.log(property1);
}

這個功能我感受可用性爲0,基本不用,with就是據對禁止的.

hasInstance

對於 instance運算符,爲此操做添加一個鉤子,第一參數是instance的左值,咱們能夠返回true|false來定義運算符的返回值

var obj1 = {
    [Symbol.hasInstance](instance){
        return Array.isArray(Array)
    }
}
class Array1 {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}
[] instance obj1  //true
console.log([] instanceof Array1);  //true

isConcatSpreadable

表示[].concat是否能夠展開,默認是true.

let arr = [1,2];
arr.concat([3,4],5)  //[1,2,3,4,5]

arr[Symbol.isConcatSpreadable] = false;
arr.concat([3,4],5)  //[[1,2],3,4,5]

// 也能夠把[3,4]提出來處理
let arr2 = [3,4]
arr2[Symbol.isConcatSpreadable] = false;
arr.concat(arr2,5); //[[1,2],[3,4],5]

只有在數組中這個symbol屬性爲false,concat操做時,就不會去解構。那麼是否是意味着屬性設置爲ture,沒有意義了?對於數組來講是的,由於它默認就是true,但是對於類數組對象,它還有一個小功能:

// (續)
arr.concat({length:2,0:3,1:4,[Symbol.isConcatSpreadable]:true}) //[1,2,3,4]

match & replace & split & search

一些字符串的操做方法,一塊兒都說了,大概都一個意思,就是接受一個對象,而後實現一個鉤子處理的函數,並返回其處理結果,它們都是能夠接收正則的方法,在ES6以前,若是咱們須要對字符串有比較複雜的操做基本上都是在方法外部的,必

class MyMatch {
    [Symbol.match](string){return string.indexOf('world') }
}

'hello world'.match(new MyMatch()); //6

class MyReplace{
  [Symbol.replace](string) {
     return 'def'
  }
}

'abcdef'.replace(new MyReplace(),'xxx'); //'abcxxx'

class mySplit {
    [Symbol.split](val){
        return val.split('-');
    }
}

"123-123-123".split(new mySplit());  //['123','123','123']

class MySearch {
    constructor(value) {
        this.value = value;
    }
    [Symbol.search](string) {
        return string.indexOf(this.value);
    }
}
var fooSearch = 'foobar'.search(new MySearch('foo'));  //0
var barSearch = 'foobar'.search(new MySearch('bar'));  //3
var bazSearch = 'foobar'.search(new MySearch('baz'));  //-1

practice

  1. 能夠經過Symbol實現以上的功能性方法,好比添加 Iterator 接口,讓對象隊友接口特性,實際開發中,我估計不多會用到,卻是以爲 sanycIterator 是將來的前景,目前還在草案階段
  2. 對於Symbol作爲鍵值的做用,很尷尬,實際開發中,這個我也沒使用過,目前爲止,只須要記住它有unique性,好比咱們想要在一個對象中添加兩個同樣的key名,這種需求很不常見,
var firstPerson = Symbol("peter");
var secondPerson = Symbol("peter");
var persons = {[firstPerson]:"first", [secondPerson]:"pan"};

總結

Symbol更多的是在使用和語言自己層面暴露更多的使用方式和特性(on Object type),是的,它只以key的方式存在Object當中,在一切皆爲對象中,它爲 Next ECMScript Standard 提供了更多的可能性擴展性,這也是ES6中作的最大改變方面之一,雖不經常使用但咱們仍是要總結學習一下,以便在極端狀況下應變自如,若是有什麼文章中沒有涉及到的點,歡迎補充! 注: 尤爲是使用場景方面

相關文章
相關標籤/搜索