理解 Es6 中的 Symbol 類型

前言

在 Es6 中引入了一個新的基礎數據類型:Symbol,對於其餘基本數據類型(數字number,布爾boolean,null,undefined,字符串string)想必都比較熟悉,可是這個Symbol平時用得不多,甚至在實際開發中以爲沒有什麼卵用,可以涉及到的應用場景屈指可數.前端

每每在面試的時候,屢面不爽.下面一塊兒來看看的這個數據類型的java

具體解決的問題

在 Es5 的對象屬性名中都是字符串,當一對象的屬性名出現重複時,後者每每會覆蓋前者.web

若使用Symbol就可以保證每一個屬性的名字都是獨一無二的,至關於生成一個惟一的標識 ID,這樣就從根本上防止屬性名的衝突面試

Symbol 類型

symbolEs6規範引入的一項新的特性,表示獨一無二的值,概括爲JS語言的第 7 種數據類型,它是經過Symbol函數生成跨域

經過Symbol()函數來建立生成一個Symbol實例數組

let s1 = Symbol();
console.log(typeof s1); //symbol console.log(Object.prototype.toString.call(s1)); // [object Symbol] 複製代碼

在上面示例代碼中,用typeof進行了類型的檢測,它返回的是Symbol類型,而不是什麼string,object之類的瀏覽器

Es5 中原有的對象的屬性名是字符串類型中拓展了一個Symbol類型,也就是說,如今對象的屬性名有兩種類型微信

  • 字符串類型
  • Symbol 類型

注意app

Symbol 函數前不能使用new關鍵字,不然就會報錯,這是由於生成的Symbol是一個原始類型的值,它不是對象編輯器

由於不是對象,因此也不能添加屬性,它是一種相似於字符串的數據類型,能夠理解爲是在字符串類型的一種額外的拓展

Symbol函數能夠接收一個字符串作爲參數,它是對該Symbol實例的一種描述,主要是爲了在控制檯顯示

Symbol 的描述是可選的,僅用於調試目的或轉爲字符串時,進行區分,不是訪問 symbol 自己

可使用Symbol().description會返回Symbol()的實例描述的具體內容,若是有值,則會返回該描述,若無則會返回undefined

descriptionSymbol的一個靜態屬性

當使用字符串定義對象的屬性名時,若出現同名屬性,則會出現屬性覆蓋問題,而使用Symbol類型定義的對象屬性名,則不會,它是獨一無二的,每調用一次Symbol()都會生成一個惟一的標識,即便是使用Symbol() 生成的實例描述相同,但它們依舊不相等,總會返回false 以下代碼所示

let s1 = Symbol('itclanCoder'); // 定義了一s1變量,它是Symbol()類型,並接收了一個itclanCoder字符串,做爲該Symbol的實例
let s2 = Symbol('itclanCoder'); // 實例化了一個s2,Symbol()類型 console.log(s1.description); // itclanCoder console.log(s1.description); // itclanCoder console.log(s1 === s2); // false 複製代碼

從第 5 行代碼比較結果看出,s1s2是兩個不一樣的Symbol值,這裏讓Symbol接受一個參數,若是不加參數,它們在控制檯輸出的都是Symbol,即便參數相同,可是它們依舊是兩個不一樣的Symbol

若是您但願使用擁有同一個Symbol值,那該怎麼辦?在 Es6 中,提供了一個Symbol.for()方法能夠實現,它接受一個字符串做爲參數 而後搜索有沒有以該參數做爲名稱的Symbol值

若是有,就返回這個Symbol值,不然就新建一個以該字符串爲名稱的Symbol值,並會將它註冊到全局壞境中

let s1 = Symbol.for('itclanCoder');
let s2 = Symbol.for('itclanCoder'); console.log(s1 === s2); // true 複製代碼

在上面的示例代碼中,s1s2 都是Symbol實例化出來的值,可是它們都是由Symbol.for方法生成的,指向的是同一個值,地止

  • SymbolSymbol.for 的區別

比較

共同點: 都會生成新的Symbol 不一樣點: Symbol.for()會被登記在全局壞境中供搜索,而Symbol()不會,Symbol.for()不會每次調用就返回一個新的Symbol類型的值,而是會先檢查給定的key是否已經存在,若是不存在纔會新建一個Symbol

如:調用Symbol.for('itclanCoder')100 次,每次都會返回同一個Symbol值,可是調用Symbol('itclanCoder')100 次,會返回 100 個不一樣的Symbol

Symbol.for("itclanCoder") === Symbol.for("itclanCoder") // true
 Symbol("itclanCoder") === Symbol("itclanCoder") // false 複製代碼

在上面代碼中,因爲Symbol()寫法沒有登記機制,因此每次調用都會返回一個不一樣的值,也就是每次都會在棧內存中從新開闢一塊空間

也能夠經過Symbol.keyFor()方法返回一個已登記的Symbol類型值的key,經過該方法檢測是否有沒有全局註冊

let s1 = Symbol.for("itclan");
console.log(Symbol.keyFor(s1)) // "itclan"  let s2 = Symbol("itclan"); console.log(Symbol.keyFor(s2)) // undefined  複製代碼

在上面的代碼中,變量s2屬於未被登記的Symbol值,因此就返回undefined

注意

Symbol.for()是爲Symbol值登記的名字,在整個全局做用域範圍內都起做用

function foo() {
 return Symbol.for('itclan'); }  const x = foo(); const y = Symbol.for('itclan'); console.log(x === y); // true 複製代碼

在上面代碼中,Symbol.for('itclan')是在函數內部運行的,可是生成的 Symbol 值是登記在全局環境的。因此,第二次運行Symbol.for('itclan')能夠取到這個 Symbol 值

  • 應用場景: Symbol.for()

這個全局記錄特性,能夠用在不一樣的iframeservice worker中取到同一個值

在前端開發中,有時候會用到iframe,可是iframe之間相互隔離的,有時候想要取到不一樣的iframe中同一份數據,那麼這個Symbol.for()就派上用場了的

以下示例代碼所示

let iframe = document.createElement('iframe');
iframe.src = String(window.location); document.body.appendChild(iframe);  iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo') // true 複製代碼

在上面代碼中,iframe窗口生成的Symbol 值,能夠在主頁面拿獲得,在整個全局做用域內均可以取到

Symbol 應用場景

  • 應用場景 1-使用Symbol來做爲對象屬性名(key)

在 Es6 以前,一般定義或訪問對象的屬性都是使用字符串,以下代碼所示

let web = {
 site: "http://itclan.cn",  name: "itclanCoder" } console.log(web['site']); // http://itclan.cn console.log(web['name']); // itclanCoder 複製代碼

訪問變量對象的屬性,除了能夠經過對象.屬性名的方式外,能夠經過對象['屬性名']的方式進行訪問,若是一個對象中出現了同名屬性那麼後者會覆蓋前者

因爲每調用一次Symbol函數,生成的值都是不相等的,這意味着Symbol值能夠做爲標識符,用於對象的屬性名,就能保證不會出現同名的屬性

針對一個對象由多個模塊構成的狀況就變得很是有用了的,使用Symbol能放置某一個鍵被不當心改寫或覆蓋

Symbol能夠用於對象屬性的定義和訪問

以下示例代碼所示

const PERSON_NAME = Symbol();
const PERSON_AGE = Symbol();  let person = {  [PERSON_NAME]: "隨筆川跡" }  person[PERSON_AGE] = 20;  console.log(person[PERSON_NAME]) // 隨筆川跡 console.log(person[PERSON_AGE]) // 20 複製代碼

在上面的示例代碼中,使用Symbol建立了PERSON_NAME,PERSON_AGE兩個Symbol類型,可是在實際開發中卻帶來了一些問題

當您使用了Symbol做爲對象的屬性key後,你若想對該對象進行遍歷,因而用到了Object.keys(),for..in,for..of,Object.getOwnPropertyNames()、JSON.stringify()進行枚舉對象的屬性名

你會發現使用Symbol後會帶來一個很是使人難以接受的現實,以下示例代碼所示

let person = {
 [Symbol('name')]: '隨筆川跡',  age: 20,  job: 'Engineer' } console.log(Object.keys(person)) // ["age", "job"] for(var i in person) {  console.log(i); // age job }  Object.getOwnPropertyNames(person) // ["age", "job"] JSON.stringify(person); // "{"age":20,"job":"Engineer"}" 複製代碼

經過上面的示例代碼結果可知,Symbol類型實例化出的key是不能經過Object.keys(),for..in,for..of,來枚舉的

它也沒有包含子自身屬性集合Object.getOwnPropertyName()當中,該方法沒法獲取到

利用該特性,咱們能夠把一些不須要對外操做和訪問的屬性使用Symbol來定義

這樣,咱們在定義接口的數據對象時,能夠決定對象的哪些屬性,對內私有操做與對外公有操做變得可控,更加的方便

使用常規的方法,沒法獲取到以Symbol方式定義對象的屬性,在 Es6 中,提供了一個專門針對Symbol的 API

Object.getOwnPropertySymbols()方法,能夠獲取指定對象的全部Symbol屬性名,該方法會返回一個數組

它的成員是當前對象的全部用做屬性名的 Symbol 值

let person = {
 [Symbol('name')]: '隨筆川跡',  age: 20,  job: 'Engineer' }  // 使用Object的API Object.getOwnPropertySymbols(person) // [Symbol(name)]  複製代碼

以下是Object.getOwnPropertySymbols()方法與for..in循環,Object.getOwnPropertyNames方法進行對比的例子

const person = {};
const name = Symbol('name');  person[name] = "隨筆川跡"  for(let i in person) {  console.log(i); // 無任何輸出 }  Object.getOwnPropertyNames(person); // [] Object.getOwnPropertySymbols(person); // [Symbol('name')] 複製代碼

在上面代碼中,使用for...in循環和Object.getOwnPropertyNames()方法都得不到 Symbol 鍵名,須要使用Object.getOwnPropertySymbols()方法。

若是想要獲取所有的屬性,可使用一個新的 API,Reflect.ownKeys()方法能夠返回全部類型的鍵名,包括常規鍵名和 Symbol 鍵名

let person = {
 [Symbol('name')]: "川川",  enum: 2,  nonEnum: 3 };  Reflect.ownKeys(person) // ["enum", "nonEnum", Symbol(name)] 複製代碼

正因爲以Symbol 值做爲鍵名,不會被常規方法(for..in,for..of)遍歷獲得。咱們能夠利用這個特性,爲對象定義一些非私有的、但又但願只用於內部的方法,達到保護私有屬性的目的

  • 應用場景 2:使用 Symbol 定義類的私有屬性/方法

JavaScript 是一弱類型語言,弱並非指這個語言功能弱,而所指的是,它的類型沒有強制性,是沒有如java等面嚮對象語言的訪問控制關鍵字private的,類上全部定義的屬性和方法都是公開訪問的,固然在TypeScript中新增了一些關鍵字,解決了此問題的

有時候,類上定義的屬性和方法都能公開訪問,會形成一些困擾

而有了Symbol類的私有屬性和方法成爲了實現

以下示例代碼

let size = Symbol('size');  // 聲明定義了一個size變量,類型是Symbol(),類型描述內容是size
 class Collection { // class關鍵字定義了一個Collection類  constructor() { // 構造器`constructor`函數  this[size] = 0; // 在當前類上私有化了一個size屬性  }   add(item) { // Collection類下的一個方法  this[this[size]] = item;  this[size]++;  }   static sizeOf(instance) { // 靜態屬性  return instance[size];  } }  let x = new Collection(); // 實例化x對象 Collection.sizeOf(x) // 0  x.add('foo'); // 調用方法 Collection.sizeOf(x) // 1  Object.keys(x) // ['0'] Object.getOwnPropertyNames(x) // ['0'] Object.getOwnPropertySymbols(x) // [Symbol(size)] 複製代碼

上面代碼中,對象 xsize 屬性是一個 Symbol 值,因此 Object.keys(x)Object.getOwnPropertyNames(x)都沒法獲取它。這就形成了一種非私有的內部方法的效果

  • 應用場景 3-模塊化機制

結合Symbol與模塊化機制,類的私有屬性和方法完美實現,以下代碼所示 在文件a.js

const PASSWORD = Symbol();  // 定義一個PASSWORD變量,類型是Symbol
 class Login() { // class關鍵字聲明一個Login類  constructor(username, password) { // 構造器函數內初始化屬性  this.username = username;  this[PASSWORD] = password;  }   checkPassword(pwd) {  return this[PASSWORD] === pwd;  }  } export default Login; 複製代碼

在文件b.js

import Login from './a'
 const login = new Login('itclanCoder', '123456'); // 實例化一個login對象  login.checkPassword('123456'); // true login.PASSWORD; // 訪問不到 login[PASSWORD]; // 訪問不到 login['PASSWORD'] // 訪問不到  複製代碼

由於經過Symbol定義的PASSWORD常量定義在a.js模塊中,外面的模塊是獲取不到這個Symbol的,在外部沒法引用這個值,也沒法改寫,也不可能在在建立一個如出一轍的Symbol出來

由於Symbol是惟一的

a.js模塊中,這個PASSWORDSymbol類型只能在當前模塊文件(a.js)中內部使用,因此使用它來定義的類屬性是沒有辦法被模塊外訪問到的

這樣就達到了一個私有化的效果

  • 應用場景 4-使用Symbol來替代常量

在使用React中,結合Redux作公共數據狀態管理時,當想要改變組件中的某個狀態時,reducer是一個純函數,它會返回一個最新的狀態給store,返回的結果是由actionstate共同決定的

action是一個對象,有具體的類型type值,若是你寫過幾行Redux的代碼,就會經常看到,進行action的拆分,將事件動做的類型定義成常量

const CHANGE_INPUT_VALUE = 'CHANGE_INPUT_VALUE';  // 監聽input框輸入值的常量
const ADD_INPUT_CONTENT = 'ADD_INPUT_CONTENT'; // 添加列表 const DELETE_LIST = 'DELETE_LIST'; // 刪除列表  function reducer(state, action) {  const newState = JSON.parse(JSON.stringify(state));  switch(action.type) {  case CHANGE_INPUT_VALUE:  // ...  case ADD_INPUT_CONTENT:  // ...  case DELETE_LIST;  // ...  default:  return state;  }  } 複製代碼

以上代碼在Redux中很常見,將action對象中的type值,給抽離出來,定義一個常量存儲,來表明一種業務邏輯,一般但願這些常量是惟一的,在Redux中定義成常量,是爲了便於調試查錯

經常由於取type值時,很是苦惱.

如今有了Symbol,改寫一下,就能夠這樣

const CHANGE_INPUT_VALUE = Symbol()
const ADD_INPUT_CONTENT = Symbol(); const DELETE_LIST = Symbol()  function reducer(state, action) {  const newState = JSON.parse(JSON.stringify(state));  switch(action.type) {  case CHANGE_INPUT_VALUE:  // ...  case ADD_INPUT_CONTENT:  // ...  case DELETE_LIST;  // ...  default:  return state;  }  } 複製代碼

經過Symbol定義字符串常量,就保證了三個常量的值惟一性

劃重點

  • 常量使用Symbol值最大的好處,就是其餘任何值都不可能有相同的值了,能夠保證常量的惟一性,所以,能夠保證上面的switch語句按照你設計的方式條件去工做

  • Symbol值做爲屬性名時,該屬性是公開屬性,不是私有屬性

  • 應用場景 5-註冊和獲取全局的`Symbol

在瀏覽器窗口(window)中,使用Symbol()函數來定義生成的Symbol實例是惟一的

可是若應用涉及到多個window,最多見的就是在各個頁面窗口中嵌入iframe了,並在各個iframe頁面中取到來自同一份公共的數據源

也就是在各個window中使用的某些Symbol但願是同一個,那麼這個時候,使用Symbol()就不行不通了

由於用它在不一樣window中建立的Symbol實例老是惟一的,而咱們須要的是在全部這些window環境下保持一個共享的Symbol值。

在這種狀況下,咱們就須要使用另外一個 API 來建立或獲取Symbol,那就是Symbol.for(),它能夠註冊或獲取一個window間全局的Symbol實例,它是Symbol的一個靜態方法

這個在前面已經提到過一次,這個仍是有那麼一點點用處,因此在提一嘴的

以下示例代碼所示

let gs1 = Symbol.for('global_symbol_1')  //註冊一個全局Symbol
let gs2 = Symbol.for('global_symbol_1') //獲取全局Symbol  console.log(gs1 === gs2 ) // true 複製代碼

通過Symbol.for()實例化出來的Symbol字符串類型,只要描述的內容相同,那麼不光是在當前window中是惟一的,在其餘全局範圍內的window也是惟一的,而且相同

該特性,如果建立跨文件可用的symbol,甚至跨域(每一個window都有它本身的全局做用域) , 可使用 Symbol.for()取到相同的值

也就是說,使用了Symbol.for()在全局範圍內,Symbol類型值能夠共享

注意事項

  • Symbol 值不能與其餘類型的值進行運算-會報錯
let symItclan = Symbol('itclan');
 console.log("主站" + symItclan) console.log(`主站 ${symItclan}`) // Uncaught TypeError: Cannot convert a Symbol value to a string 複製代碼
  • Symbol能夠顯示轉爲字符串
let SyItclanCoder = Symbol('https://coder.itclan.cn');
 console.log(String(SyItclanCoder)) // Symbol(https://coder.itclan.cn) console.log(SyItclanCoder.toString()) // Symbol(https://coder.itclan.cn) 複製代碼
  • Symbol值能夠轉爲布爾值,可是不能轉爲數值
let sym = Symbol();
console.log(Boolean(sym)) // true console.log(!sym) // false  if (sym) {  // ... }  Number(sym) // TypeError Cannot convert a Symbol value to a number sym + 2 // TypeError 複製代碼

由上面的錯誤提示能夠看出,Symbol不能轉換爲數字,沒法作相應的運算

  • Symbol函數不能使用new命令

Symbol函數前不能使用new命令,不然就會報錯,Symbol是一個原始類型的值,不是對象,它是相似字符串的數據類型

  • Symbol值做爲對象屬性名時,不能用點運算符

Symbol值做爲對象的屬性名時,訪問它時,不能用點運算符

const username = Symbol();
const person = {};
person.username = '隨筆川跡';
person[username]; // undefined
person['username']; // 隨筆川跡
複製代碼

第 4 行代碼值爲undefined,由於點運算符後面老是字符串,因此不會讀取username做爲標識符名所指代的那個值

致使person對象的屬性名其實是一個字符串,而不是一個Symbol

因而可知:在對象內部,使用Symbol類型定義屬性名時,Symbol值必須放在中括號之中

let s = Symbol();
let obj = {  [s]: function(arg) {  return arg;  } }  obj[s]("itclanCoder") 複製代碼

在上面的代碼中,若是變量s不放在中括號中,該屬性的鍵名就是字符串s,而不是定義Symbol類型值

總結

本文主要介紹了Es6Symbol的常見使用,Symbol是一種新的基礎類型,它形式字符串的數據類型,是字符串類型的一種額外拓展

經常使用於做爲對象屬性的鍵名,每一個從Symbol()返回的symbol值都是惟一的,可保證對象的每一個屬性名的惟一性,可解決屬性名的衝突問題

Symbol()函數會返回symbol類型的值,該類型具備靜態屬性(如Symbol().description,)和靜態方法(Symbol.for(),Symbol.keyFor())

固然也介紹了Symbol的一些常見應用場景,做爲對象的屬性名(key),定義類的私有屬性和方法,替代常量,以及註冊全局Symbol等,以及一些注意事項

關於Symbol暫且就這麼多,仍是得多多使用

更多內容,您可關注微信itclanCoder公衆號,一個只傳遞和分享給你帶來啓發智慧有用的號

原文出處-https://coder.itclan.cn/-理解Es6中的Symbol類型

相關文章
相關標籤/搜索