ES6 爲 JavaScript 引入了一種新的基本類型:Symbol,它由全局 Symbol() 函數建立,每次調用 Symbol()函數,都會返回一個惟一的 Symbol。javascript
let symbol1 = Symbol(); let symbol2 = Symbol(); console.log( symbol1 === symbol2 ); > false
由於每一個 Symbol 值都是惟一的,所以該值不與其它任何值相等。html
Symbol 是 JavaScript 中的新原始類型。java
console.log( typeof symbol1 ); > "symbol"
Symbol 充當惟一的對象鍵。node
let myObject = { publicProperty: 'Value of myObject[ "publicProperty" ]' }; myObject[ symbol1 ] = 'Value of myObject[ symbol1 ]'; myObject[ symbol2 ] = 'value of myObject[ symbol2 ]'; console.log( myObject ); > Object > publicProperty: "Value of myObject[ "publicProperty" ]" > Symbol(): "Value of myObject[ symbol1 ]" > Symbol(): "value of myObject[ symbol2 ]" > __proto__: Object console.log( myObject[ symbol1 ] ); > Value of myObject[ symbol1 ]
當控制檯打印myObject
時,你能看到兩個 Symbol 值都存儲在對象中。"Symbol()"
是調用toString()
的返回值,此值表示控制檯中存在 Symbol 鍵。若是咱們想訪問正確的 Symbol,能夠檢索相應的值。es6
Symbol 鍵的屬性不會在對象的 JSON 中顯示,也不會在 for-in 循環和Object.keys
中被枚舉出來:markdown
JSON.stringify( myObject ) > "{"publicProperty":"Value of myObject[ \"publicProperty\" ] "}" for( var prop in myObject ) { console.log( prop, myObject[prop] ); } > publicProperty Value of myObject[ "publicProperty" ] console.log( Object.keys( myObject ) ); > ["publicProperty"]
即便 Symbol 鍵的屬性沒有在上述案例中出現,這些屬性在嚴格意義上也不是徹底私有的。Object.getOwnPropertySymbols
提供了一種檢索對象的 Symbol 鍵的方法。閉包
Object.getOwnPropertySymbols(myObject) > [Symbol(), Symbol()] myObject[ Object.getOwnPropertySymbols(myObject)[0] ] > "Value of myObject[ symbol1 ]"
若是你使用 Symbol 鍵來表示私有變量,要確保不要用Object.getOwnPropertySymbols
來檢索可能私有化的屬性。在這種狀況下,Object.getOwnPropertySymbols
的惟一使用狀況就是測試和調試。函數
只要你遵循上述規則,從代碼開發的角度來看,對象鍵值是私有的,但在實際狀況中,其餘人仍能訪問你的私有值。測試
雖然 Symbol 鍵不能被for...of
,擴展運算符和Object.keys
枚舉,但它們仍被包含在淺拷貝里:ui
clonedObject = Object.assign( {}, myObject ); console.log( clonedObject ); > Object > publicProperty: "Value of myObject[ "publicProperty" ]" > Symbol(): "Value of myObject[ symbol1 ]" > Symbol(): "value of myObject[ symbol2 ]" > __proto__: Object
正確命名 Symbol 對指明其用途相當重要,若是你須要額外的語義指導,還可在 Symbol 上附上一個描述。Symbol 的描述體如今 Symbol 的字符串值中。
let leftNode = Symbol( 'Binary tree node' ); let rightNode = Symbol( 'Binary tree node' ); console.log( leftNode ) > Symbol(Binary tree node)
始終提供 Symbol 的描述,並始終保持描述的惟一性。若是用 Symbol 訪問私有屬性,請將其描述視爲變量名。
若是你將相同的描述傳遞給兩個 Symbol,它們的值仍不相同。
console.log( leftNode === rightNode ); > false
ES6 有一個用於建立 Symbol 的全局資源:Symbol 註冊表,它爲字符串和 Symbol 提供了一對一的關係。註冊表使用 Symbol.for( key )
返回 Symbol。
當出現key1 === key2
時就會有Symbol.for( key1 ) === Symbol.for( key2 )
。這種對應關係甚至是跨 service worker 和 iframe 的。
let privateProperty1 = Symbol.for( 'firstName' ); let privateProperty2 = Symbol.for( 'firstName' ); myObject[ privateProperty1 ] = 'Dave'; myObject[ privateProperty2 ] = 'Zsolt'; console.log( myObject[ privateProperty1 ] ); // Zsolt
由於 Symbol 註冊表中的 Symbol 值和字符串之間有一一對應的關係,因此咱們也能夠檢索字符串鍵。使用Symbol.keyFor
方法。
Symbol.keyFor( privateProperty1 ); > "firstName" Symbol.keyFor( Symbol() ); > undefined
即便 Symbol 不能使屬性私有,它們也能用做帶有私有屬性的符號。你能夠使用 Symbol 來分隔公有和私有屬性的枚舉,Symbol 能使它更清楚。
const _width = Symbol('width'); class Square { constructor( width0 ) { this[_width] = width0; } getWidth() { return this[_width]; } }
只要你能隱藏_width
就好了,隱藏_width
的方法之一是建立閉包:
let Square = (function() { const _width = Symbol('width'); class Square { constructor( width0 ) { this[_width] = width0; } getWidth() { return this[_width]; } } return Square; } )();
這樣作的好處是,他人很難訪問到咱們對象的私有_width
值,並且也能很好地區分,哪些屬性是公有的,哪些屬性是私有的。但這種方法的缺點也很明顯:
Object.getOwnPropertySymbols
,咱們能夠使用 Symbol 鍵。若是你要用 Symbol 來表示私有字段,那你須要代表哪些屬性不能被公開訪問,如如有人試圖違背這一規則,理應承擔相應的後果。
枚舉容許你定義具備語義名稱和惟一值的常量。假定 Symbol 的值不一樣,它們能爲枚舉類型提供最好的值。
const directions = { UP : Symbol( 'UP' ), DOWN : Symbol( 'DOWN' ), LEFT : Symbol( 'LEFT' ), RIGHT: Symbol( 'RIGHT' ) };
當使用 Symbol 做爲變量時,咱們沒必要創建可用標識符的全局註冊表,也沒必要費心思想標識符名字,只須要建立一個 Symbol 就好了。
外部庫的作法也是這樣。
這裏有一些比較經常使用的 Symbol,用以訪問和修改內部 JavaScript 行爲。你能夠用它們從新定義內置方法。運算符和循環。
演練1.用下劃線來表示字段的私有,有什麼利弊?用這種方法和 Symbol 比較。
let mySquare { _width: 5, getWidth() { return _width; } }
利:
弊:
Object.keys
和for..of
循環枚舉。演練2. 模擬 JavaScript 中的私有字段。
解決方案:當涉及到構造函數時,能夠使用var
, let
, 或 const
在構造函數中聲明私有成員。
function F() { let privateProperty = 'b'; this.publicProperty = 'a'; } let f = new F(); // f.publicProperty returns 'a' // f.privateProperty returns undefined
爲了對類使用相同的方法,咱們必須放置方法定義:在可訪問私有屬性的做用域中的構造函數方法中使用私有屬性的方法。咱們將使用Object.assign
來達到此目的。(靈感來自Managing private data of ES6 classes)
class C { constructor() { let privateProperty = 'a'; Object.assign( this, { logPrivateProperty() { console.log( privateProperty ); } } ); } } let c = new C(); c.logPrivateProperty();
字段privateProperty
在對象c
中不可訪問。
該解決方案也適用於咱們擴展 C 類。