JS.next

JavaScriptの最新実装情報を追うブログ

Symbolについて

概要

ES2015ではUndefinedNullBooleanNumberStringObjectの6つの型に加えて、新しい型Symbolが導入された。
シンボルはプリミティブ型で、文字列のようにプロパティのキーとして使える特徴を持つ。


振る舞い

シンボルはSymbolコンストラクタを呼ぶことで作られ、その時に引数として文字列を渡せば名前を持たせることができる。

sym1 = Symbol()
sym2 = Symbol('name')


typeof演算子で評価すると"symbol"が返される。

typeof sym1  // "symbol"


Symbolコンストラクタをnew付きで呼び出すことは出来ないが、シンボルをObjectコンストラクタに渡すことでラップすることはできる。

new Symbol()  // TypeError

symobj = Object(sym2)
typeof symobj  // "object"


各シンボルはユニークであり、原則他のシンボルと同じものを作り出すことはできない。

sym1 === sym1  // true
Symbol('abc') === Symbol('abc')  // false


Symbol.prototype.valueOf は自分自身のシンボルを返す。
Symbol.prototype.toString は名前が含まれた表記を返す。

sym1 === sym1.valueOf()   // true
sym2 === symobj.valueOf() // true
sym2 === symobj           // true
sym1.toString()           // "Symbol()"
sym2.toString()           // "Symbol(name)"


但しシンボルやシンボルオブジェクトを数値型や、暗黙的に文字列型に変換しようとするとエラーになる。
真偽値として評価するとtrueとして扱われる。

String(sym2)     // "Symbol(name)"
sym2.toString()  // "Symbol(name)"
'' + sym2        // TypeError

Number(sym2)     // TypeError
+sym2            // TypeError

Boolean(sym2)    // true
!!sym2           // true


プロパティのキーとして使う

シンボルは文字列と同じようにプロパティのキーとして使える。

obj = {}
obj[sym1] = 123

obj[sym1] * 2  // 246


但し文字列と違ってそのシンボルを直接持っていないとプロパティの値にアクセスできないので、表に出したくない(プライベートっぽい)メンバを実現するのに使える。

// 文字列キーを使った場合
A = function () {
  var n = 1

  function A() {
    this._id = n++
  }

  A.prototype.getId = function () {
    return this._id
  }

  return A
}()


a = new A
// 簡単にアクセスできるし、簡単に列挙されてしまう、何より見栄えが悪い
a._id           // 1
Object.keys(a)  // ["_id"]
// シンボルキーを使った場合
B = function () {
  var idsym = Symbol('id')
  var n = 1

  function B() {
    this[idsym] = n++
  }

  B.prototype.getId = function () {
    return this[idsym]
  }

  return B
}()


b = new B
// 隠蔽度が高い
b._id          // undefined
Object.keys(b) // []

// 完全に隠蔽されるわけではない
idsym = Object.getOwnPropertySymbols(b)[0]  // Symbol(id)
b[idsym]       // 1


シンボルを登録する

前記したように、シンボルはユニークであるが、異なる場所間で同一のシンボルを共有したい場合がある。

sym1 = Symbol('abc')
sym2 = Symbol('abc')

sym1 === sym2  // false
  // 当然一致しない


その場合にSymbol.keyを使うことで、初回は文字列に紐付けられた新しいシンボルが返される。
そしてその後に同じキーでSymbol.forされると、登録されたシンボルが返される。

sym1 = Symbol.for('abc')  // 新しく作られ、登録される
sym2 = Symbol.for('abc')  // 登録されたものが返される
sym3 = Symbol('abc')      // ユニークなシンボル

sym1 === sym2  // true
sym2 === sym3  // false


グローバルが異なる環境同士でも問題なく共有することができる。

sym1 =  thisGlobal.Symbol.for('abc')
sym2 = otherGlobal.Symbol.for('abc')

sym1 === sym2  // true


Symbol.keyForでシンボルがどういう文字列に紐付けられているかを確認できる。

sym1 = Symbol.for('abc')
sym2 = Symbol('abc')

Symbol.keyFor(sym1)  // "abc"
Symbol.keyFor(sym2)  // undefined


ビルトインシンボルについて

ES2015では特別な意味を持つシンボルがいくつか定義されており、それらのシンボルプロパティをオブジェクトに作っておくと、特定の場面で参照され値が利用される。

例:@@iterator(="iterator"シンボル)

  // ビルトインシンボルはSymbolオブジェクトに生えているので、それを貰う
iteratorSymbol = Symbol.iterator  

obj = { a: 1, b: 2, c: 3 }

  // オブジェクトに"iterator"シンボルプロパティを設定する
obj[iteratorSymbol] = function () { return Object.keys(this).values() }

  // これはイテレータが期待される場面で利用される(↓リンク先参照)


実装済のビルトインシンボル


実装されたバージョン

V8 3.17.7 3.25.1(修正) 3.25.23(symbol registry) 3.28.26(修正) 3.28.59(デフォルト有効)  3.28.71(修正)  3.29.61(修正:「String(sym)」は「TypeError」にならない)
Chrome 38M(デフォルト有効)