ES6 的 Symbol 類型及使用案例

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

全局 Symbol 註冊表

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 來分隔公有和私有屬性的枚舉,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 鍵。
  • 若是要寫不少的代碼,這會使得開發者的體驗不佳,訪問私有屬性不像 Java 或 TypeScript 那樣方便。

若是你要用 Symbol 來表示私有字段,那你須要代表哪些屬性不能被公開訪問,如如有人試圖違背這一規則,理應承擔相應的後果。

建立枚舉類型

枚舉容許你定義具備語義名稱和惟一值的常量。假定 Symbol 的值不一樣,它們能爲枚舉類型提供最好的值。

const directions = {
    UP   : Symbol( 'UP' ),
    DOWN : Symbol( 'DOWN' ),
    LEFT : Symbol( 'LEFT' ),
    RIGHT: Symbol( 'RIGHT' )
};

避免名稱衝突

當使用 Symbol 做爲變量時,咱們沒必要創建可用標識符的全局註冊表,也沒必要費心思想標識符名字,只須要建立一個 Symbol 就好了。

外部庫的作法也是這樣。

知名 Symbol

這裏有一些比較經常使用的 Symbol,用以訪問和修改內部 JavaScript 行爲。你能夠用它們從新定義內置方法。運算符和循環。

演練

演練1.用下劃線來表示字段的私有,有什麼利弊?用這種方法和 Symbol 比較。

let mySquare {
    _width: 5,
    getWidth() { return _width; }
}

利:

  • 開發者體驗佳
  • 不會形成複雜的代碼結構

弊:

  • 屬性僅被表示爲私有,在實踐中並非私有的,容易被破解
  • 不一樣於 Symbol,這種方式的公有和私有屬性沒有很好地區分,私有屬性出如今對象的公有接口中,它們使用能被擴展運算符,Object.keysfor..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 類。

原文:ES6 Symbols and their Use Cases

編譯:開源中國-達爾文

相關文章
相關標籤/搜索