在 Es6 中引入了一個新的基礎數據類型:Symbol
,對於其餘基本數據類型(數字number
,布爾boolean
,null
,undefined
,字符串string
)想必都比較熟悉,可是這個Symbol
平時用得不多,甚至在實際開發中以爲沒有什麼卵用,可以涉及到的應用場景屈指可數.前端
每每在面試的時候,屢面不爽.下面一塊兒來看看的這個數據類型的java
在 Es5 的對象屬性名中都是字符串,當一對象的屬性名出現重複時,後者每每會覆蓋前者.web
若使用Symbol
就可以保證每一個屬性的名字都是獨一無二的,至關於生成一個惟一的標識 ID,這樣就從根本上防止屬性名的衝突面試
symbol
是Es6
規範引入的一項新的特性,表示獨一無二的值,概括爲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
description
是Symbol
的一個靜態屬性
當使用字符串定義對象的屬性名時,若出現同名屬性,則會出現屬性覆蓋問題,而使用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 行代碼比較結果看出,s1
與s2
是兩個不一樣的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 複製代碼
在上面的示例代碼中,s1
和 s2
都是Symbol
實例化出來的值,可是它們都是由Symbol.for
方法生成的,指向的是同一個值,地止
Symbol
與 Symbol.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()
這個全局記錄特性,能夠用在不一樣的iframe
火service 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
來做爲對象屬性名(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
)遍歷獲得。咱們能夠利用這個特性,爲對象定義一些非私有的、但又但願只用於內部的方法,達到保護私有屬性的目的
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)] 複製代碼
上面代碼中,對象 x
的 size
屬性是一個 Symbol
值,因此 Object.keys(x)
、Object.getOwnPropertyNames(x)
都沒法獲取它。這就形成了一種非私有的內部方法的效果
結合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
模塊中,這個PASSWORD
的Symbol
類型只能在當前模塊文件(a.js
)中內部使用,因此使用它來定義的類屬性是沒有辦法被模塊外訪問到的
這樣就達到了一個私有化的效果
Symbol
來替代常量在使用React
中,結合Redux
作公共數據狀態管理時,當想要改變組件中的某個狀態時,reducer
是一個純函數,它會返回一個最新的狀態給store
,返回的結果是由action
和state
共同決定的
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
值做爲屬性名時,該屬性是公開屬性,不是私有屬性
在瀏覽器窗口(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
類型值
本文主要介紹了Es6
中Symbol
的常見使用,Symbol
是一種新的基礎類型,它形式字符串的數據類型,是字符串類型的一種額外拓展
經常使用於做爲對象屬性的鍵名,每一個從Symbol()
返回的symbol值
都是惟一的,可保證對象的每一個屬性名的惟一性,可解決屬性名的衝突問題
Symbol()
函數會返回symbol
類型的值,該類型具備靜態屬性(如Symbol().description
,)和靜態方法(Symbol.for()
,Symbol.keyFor()
)
固然也介紹了Symbol
的一些常見應用場景,做爲對象的屬性名(key),定義類的私有屬性和方法,替代常量,以及註冊全局Symbol
等,以及一些注意事項
關於Symbol
暫且就這麼多,仍是得多多使用
更多內容,您可關注微信itclanCoder公衆號,一個只傳遞和分享給你帶來啓發智慧有用的號