ES6 Symbol的特性與思考

先簡單說說什麼是Symbol

  • Symbol是ES6新增的基礎數據類型,它的特色就是獨一無二的,如同UUID同樣;
  • Symbol是函數,經過調用Symbol函數來建立Symbol數據;
  • Symbol仍是內置對象,提供一系列函數well-known Symbol方法來改變JS語言的內部行爲;

Symbol的特性與使用

示例:建立Symbol數據

  • Symbol沒有字面量的建立方式,也不能以new Symbol()構造函數的方式建立,只能經過調用Symbol([description])函數,或者Symbol.for()來建立。
// Symbol不容許以new關鍵的構造函數方式調用
new Symbol()
// Uncaught TypeError: Symbol is not a constructor

// 建立無描述的Symbol數據
let symbol1 = Symbol()

// 建立帶描述的Symbol數據
let localSymbol Symbol('desc1')

// 在全局環境建立Symbol數據
let globalSymbol = Symbol.for('desc1')

// 全局註冊表的Symbol和Symbol函數建立的Symbol是不同的
console.log(localSymbol === globalSymbol)
// 輸出:false

複製代碼

特性:Symbol老是惟一的

  • 數據老是獨一無二的,不只在函數、模塊甚至在window頂層做用域中都是惟一的
// 調用Symbol函數,在當前環境中(函數做用域/模塊做用域)建立惟一的symbol數據,雖然toString輸出的結果看起來同樣,但二者是不相等的
let f1 = Symbol('flag')
let f2 = Symbol('flag')

console.log(f1,f2)
// 輸出:Symbol(flag) Symbol(flag)

console.log(f1 === f2)
// 輸出:false
console.log(f1 === 'flag')
// 輸出false


// 調用Symbol.for(key)方法,在是全局環境中建立Symbol數據。
// 當調用方法是,會根據key名來進行冪等操做,發現不存在則建立,若是已存在則返回 
let lock = Symbol.for('flag')
let lockFlag = Symbol.for('flag')
console.log(lock === lockFlag)
// 輸出:true

// Symbol()函數和Symbol.for()建立的Symbol數據不同
console.log(f1 === lock)
// 輸出:false

// 在全局環境下若是不想建立,只想查找,可經過Symbol.keyFor()方法
console.log(Symbol.keyFor('flag')) // flag
console.log(Symbol.keyFor('test')) // undefined但不會建立
複製代碼

示例:使用Symbol來定義常量

  • 既然Symbol的特性是惟一標誌,咱們能夠用Symbol來作常量。
  • 之前咱們定義常量是這樣嬸的:
const FRUIT =  {
  APPLE: 'APPLE',
  BANANA: 'BANANA',
  STRAWBERRY: 'STRAWBERRY'  
} 

// 調用的時候,其實咱們並不關心value是什麼,只看key
console.log(FRUIT.APPLE)

//但萬一有個傻子,加了一個菠蘿,但值寫成了蘋果,判斷就會炸裂
const FRUIT =  {
  APPLE: 'APPLE',
  BANANA: 'BANANA',
  STRAWBERRY: 'STRAWBERRY',
  PINEAPPLE: 'APPLE'  // 新增
} 

// 而經過Symbol定義的話,就會避免這樣的問題
const FRUIT =  {
  APPLE: Symbol(),
  BANANA: Symbol(),
  STRAWBERRY: Symbol()  
}

function translate(FRUIT_TYPE){
  switch (FRUIT_TYPE) {
    case FRUIT.APPLE:
      console.log('蘋果')
      break;
    case FRUIT.BANANA:
      console.log('香蕉')
      break;
    case FRUIT.STRAWBERRY:
      console.log('草莓')
      break;
    default:
      console.log('未匹配')
      break;
  }
}

translate(FRUIT.APPLE)
// 輸出:蘋果
複製代碼

示例:使用Symbol來定義人名

  • 好比一個班級裏面,想經過人名來做爲惟一標識,但人名又沒辦法避免重複,經過Symbol來實現
const grade = {
  [Symbol('Lily')]: {
    address: 'shenzhen',
    tel: '186******78'
  },
  [Symbol('Annie')]: {
    address: 'guangzhou',
    tel: '183******12'
  },
  // 容許重複的名稱
  [Symbol('Lily')]: {
    address: 'beijing',
    tel: '172******10'
  },
}
複製代碼

特性:Symbol的類型判斷和類型轉換

  • 和String類型同樣,Symbol類型能夠經過typeof操做符進行類型判斷
let symbol = Symbol()
console.log(typeof Symbol)
// 輸出:symbol
複製代碼
  • 但和String類型不同的是,Symbol不會進行隱式的自動類型轉換,因此不能直接進行字符串拼接運算和算術運算。但能夠人爲的進行顯式類型轉換,好比轉成String、Boolean、Number、Object
let symbolUUID = Symbol('uuid')

// 不能直接進行字符串拼接操做
console.log(symbolUUID + '測試') 
// TypeError: Cannot convert a Symbol value to a string

// 也不能直接進行算數操做
console.log(symbolUUID + 1) 
// TypeError: Cannot convert a Symbol value to a number

// 但能夠進行三目運算的boolean判斷操做
console.log(symbolUUID ? '真' : '假')
// 輸出:真

console.log(String(symbolUUID) + '測試')
// 輸出:Symbol(uuid)測試
// 等價於symbolUUID.toString()


複製代碼

特性:Symbol可做爲對象的屬性key名

  • 根據規範,Symbol類型能夠做爲數據單獨存在,也能夠做爲對象的屬性key名。而且,對象的屬性key只能是字符串類型或者Symbol類型,沒有別的數據類型能夠做爲屬性key,Boolean不行,Number也不行。
  • 但值得注意的是,須要以{[SymbolKey]: value}數組括弧的方式來掛載。
// 做爲對象的屬性名
let desc = Symbol('desc')
let person = {
  name: 'huilin',
  sex: '男',
  [desc]: '職位:前端工程師'
}
// 或者能夠這樣賦值:person[desc] = '職位:前端工程師'
console.log(person)
// 輸出:{name: 'huilin',sex: '男',Symbol('desc'): '職位:前工程師'}

複製代碼
  • Symbol做爲屬性名時具備弱隱藏性
/* * 常規的方式獲取對象屬性,會自動忽略Symbol屬性的鍵值對的 */
// 上面的例子若是進行JSON.stringify()格式化操做,會忽略Symbol
console.log(JSON.stringify(person))
// 輸出:{name: 'huilin',sex: '男'} 

// 一樣的,像for循環這樣的常規遍歷操做,會忽略Symbol
for(key in person){
    console.log(key)
}
// 輸出: name sex

// Object.keys()會忽略Symbol
console.log(Object.keys(person))
// 輸出: [ 'name', 'sex' ]

// Object.getProperty()忽略Symbol
console.log(Object.getOwnPropertyNames(person))
// 輸出:[ 'name', 'sex' ]


/* * 僅獲取Symbol屬性的鍵值對的方法 */
console.log(Object.getOwnPropertySymbol(person))
// 輸出:[ Symbol(desc) ]


/* * 同時獲取常規屬性和Symbol屬性的方法 */

console.log(Reflect.ownKeys(person))
// 輸出:[ 'name', 'sex', Symbol(desc) ]
複製代碼

示例:經過Symbol模擬對象的私有屬性或者私有方法

  • 藉助Symbol屬性名的弱隱藏性,模擬私有屬性
// Symbol類型的屬性名
const id = Symbol()

class User {
  constructor(idVal, name, age){
    this[id] = idVal
    this.name = name
    this.age = age
  }

  checkId(id){
    return this[id] === id
  }
}

// 私有屬性,外部實例不能直接獲取
let u = new User('001', 'Jay', 40)
console.log(u.name, u.age, u[id])
// 輸出:Jay 40 001

// 可是經過對外暴露的方法,能訪問到私有屬性
console.log(u.checkId('001')) // true
console.log(u.checkId('002')) // false


複製代碼

示例:利用Symbol進行數據歸集和整合

  • 拿張鑫旭大佬打聽小美眉的例子來看,一般狀況下兩個對象合併,key相同則會覆蓋:
let info1 = {
  name: '小雪',
  age: 24,
  job: '前端工程師',
  desc: '喜歡看電影,已經有交往對象'
}

let info2 = {
  desc: '喜歡小狗,住在南山區,上下班坐公交車'
}

// 因爲使用desc是String做爲key,key相同會覆蓋
console.log(Object.assgin(info1,info2)) 
// 輸出:{name: '小雪',age: 24,job: '前端工程師',desc: '喜歡小狗,住在南山區,上下班坐公交車'}
複製代碼
  • 那改爲用Symbol做爲屬性key名會怎樣呢?Symbol不會進行覆蓋的
let info1 = {
  name: '小雪',
  age: 24,
  job: '前端工程師',
  [Symbol('desc')]: '喜歡看電影,已經有交往對象'
}

let info2 = {
  [Symbol('desc')]: '喜歡小狗,住在南山區,上下班坐公交車'
}

// 經過Symbol做爲key,合併會保留
console.log(Object.assgin(info1,info2)) 
// 輸出:{name: '小雪',age: 24,job: '前端工程師',Symbol('desc'): '喜歡看電影,已經有交往對象', Symbol('desc'): '喜歡小狗,住在南山區,上下班坐公交車'}
複製代碼
  • 可見,Symbol更關注的是value值,而不是key名。能夠思考得出,Symbol的特性就是方便對數據進行歸集和整合。
  • 拿現實中的例子來講吧,微信文章的點贊牆,數據值都是點贊,但記錄不會被覆蓋,用戶的頭像都會羅列出來;再好比簽到簿,數據值是時間,頗有多是扎堆簽到時間同樣,但也不會被覆蓋,而是把記錄羅列進來。
  • 再回到JavaScript語法層面,可能你們會以爲,名字衝突這種事情,機率很低吧?有必要專門新增一個Symbol嘛?可是你想啊,ES6的Module,導入導出是能夠起別名的;還有ES6的解構,能夠直接獲取對象的屬性名到當前環境;這樣你還以爲名字衝突的機率低嗎?
  • 因此Symbol經過歸集和整合的特性,針對基礎框架版本升級時,便於同名的方法或者變量向下兼容。

系統Symbol

  • 除了本身建立Symbol標記以外,ES6還提供了一系列內置的well-know(衆所周知)的Symbol標記,用於改變JavaScript底層API的行爲
API desc
Symbol.hasInstance 當調用instanceof運算符判斷實例時,會調用這個方法
Symbol.isConcatSpreadable 當調用Array.prototype.concat()時,判斷是否展開
Symbol.unscopables 對象指定使用with關鍵字時,哪些屬性會被with環境排除
Symbol.match 當執行str.match(obj)時,若是該屬性存在會調用它,並返回方法的返回值
Symbol.replace 當執行str.replace(obj)時調用,並返回方法的返回值
Symbol.search 當執行str.search(obj)時調用,並返回方法的返回值
Symbol.split 當執行str.split(obj)時調用,並返回方法的返回值
Symbol.iterator 當對象進行for...of循環時,調用Symbol.iterator方法,返回該對象默認遍歷器
Symbol.toPrimitive 當對象被轉換爲原始數據類型時調用,返回該對象對應的原始數據類型
Symbol.toStringTag 在該對象調用toString方法時調用,返回方法的返回值
Symbol.species 建立衍生對象時使用該屬性

參考

相關文章
相關標籤/搜索