Symbol値(=symbol)は、JavaScriptのプリミティブ値(プロパティやメソッドを持たない値)の1つである。最大の特徴は、同じ値を作れない点にある。
また、他のプリミティブ値と違い、ソースコードに直接書いて表せない。
Symbol値とその他のプリミティブ値の作成方法
以下、JavaScriptにおけるプリミティブ値とその作成方法の一覧である。
種類 | 作成方法 |
---|---|
symbol |
Symbol()関数を使う。 |
string |
' あるいは" あるいは` で囲って書くか、 String()関数を使う。 |
number |
数字を直接書くか、Number()関数を使う。 |
bigint |
n で終わる数字を直接書くか、BigInt()関数を使う。 |
boolean |
true 、false を直接書くか、Boolean()関数を使う。 |
undefined |
undefined を直接書く。 |
null |
null を直接書く。 |
Symbol値だけ、直接書く形で作成することができない。これはSymbol()関数
を呼び出すたびに、Symbol値に対して一意の値を付与し、一意性を確保していることによる。
String('x') == String('x')
はtrue
だが、Symbol('x') == Symbol('x')
はfalse
となる。
プリミティブ値とそのラッパーオブジェクト
undefined
とnull
を除き、プリミティブ値はそれぞれSymbol
、String
、Number
、BigInt
、Boolean
というオブジェクトに対応しているように見える。
実際は、プリミティブ値とこれらのオブジェクトは別物である。プリミティブ値に対してメソッドやプロパティを使おうとすると、JavaScriptは自動的にプリミティブ値をラップする。そして、そのラッパーオブジェクトのメソッドやプロパティが使われる。この仕組みをAuto-boxing
という。
初期のJavaScriptはAuto-boxingに対応しておらず、プリミティブ値のラッパーオブジェクトはコンストラクタで生成されていた。らしい。そのためString
、Number
、Boolean
の古い3つは、new
キーワードなしだとプリミティブ値、ありだとラッパーオブジェクトを返す仕様になっている。
これは冗長であるので、Symbol
やBigInt
では、Auto-boxing
のみに一本化された。そのためSymbol()
やBigInt()
にnew
キーワードをつけると、「おれ様はコンストラクタじゃねぇ」と怒られる。
Symbol
(あとBigInt
)はnew
をつけては作れない。要注意である。
Symbolとsymbol
Symbol
が何なのか混乱してきた。整理する。
Symbol
は、関数として呼び出すとsymbol
というプリミティブ値を返す組み込みオブジェクトである。MDNのSymbolの解説ではSymbol constructor
とも呼ばれている。その割にnew
をつけるとUncaught TypeError: Symbol is not a constructor
みたいなエラーが出る。
謎。
何か誤解しているのか。Symbol
ファクトリとかの方がわかりよくないだろうか。とりあえず以降は素直にSymbol
コンストラクタと呼ぶ。
Symbolコンストラクタに渡す引数
Symbol
コンストラクタには引数が渡せる。この引数は文字列とされているが、オブジェクトも数値も真偽値も渡せる。ただしいずれも自動で文字列に変換される。
この引数は、description
というプロパティアクセサを介してsymbol
値に保持される。これは、デバッグ時にシンボルを識別するために用いられる。
Symbol値の使いどころ
Symbol
値を使ったプロパティは、通常のキーとは異なる働きをする。具体的には、for...in
ループ、Object.keys()
メソッド、Object.getOwnPropertyNames()
メソッドなどで参照できない。
そのためSymbol
値の主な使いどころとしては、オブジェクトのプロパティを隠したい(不用意に上書きされたくない)場合や、メタデータを付与したいケースが挙げられる。
Well-known Symbolsとは
Symbol
値の使い方は、Symbol
コンストラクタそのものが体現している。
Symbol
コンストラクタの静的プロパティはすべてSymbol
値で定義されている。それらは、String
やArray
、RegExp
といった組み込みオブジェクトの、一部のプロパティやメソッドのキーとして使われている。
こういった組み込みオブジェクトに使われるシンボルは、ウェルノウン・シンボルと呼ばれている。組み込みオブジェクトのキーについて、シンボルと文字列がどのように使い分けられているかは知らない。単純に古い、新しいの問題ではないようだ。分かったら追記したい。
ウェルノウン・シンボルは、ドキュメント上では@@symbol_name
というように表現される。Array.prototype[@@iterator]()
のような形である。一覧はここあるいはここで確認できる。
Symbolコンストラクタの静的メソッド
Symbol
コンストラクタには、以下の2つの静的メソッドがある。
メソッド名 | 概要 |
---|---|
for(文字列) | グローバルシンボルレジストリを検索し、引数に渡された文字列がキーと一致するシンボルを返す。なければ文字列をキーにして、新たなシンボルを作って返す。 |
keyFor(シンボル) | グローバルシンボルレジストリを検索し、引数に渡されたシンボルがあれば、そのシンボルに対応するキーを返す。設定されていなければundefined を返す。 |
グローバルシンボルレジストリって何
MDNのドキュメントを呼んでいたらいきなりグローバルシンボルレジストリとかランタイムワイドシンボルレジストリとか言われて面食らった。
これは、複数の実行環境でsymbol
値を共有するための名前空間のようなものである。
symbol
値は、1つの実行環境ですら重複を許さない。そのため他のプリミティブ値のように、手軽に別の実行環境(iframeなど)へ渡すことができない。重複がないことを確認する仕組みが必要だ。
その仕組みがグローバルシンボルレジストリである。ここで、JavaScriptエンジンが全体のsymbol
値を管理する。
グローバルシンボルレジストリの操作は、Symbol.for()
メソッドとSymbol.keyFor()
メソッドで行う。
ローカルのSymbol()
コンストラクタと違い、グローバルシンボルレジストリではSymbol.for()
メソッドに渡す文字列をキーにしてシンボルが保存される。このキーは、description
アクセサプロパティとは別物である。
分かったようなわからないような……やっぱりわからない。
まとめ
- Symbolは唯一無二の文字列みたいなもの。
- Symbolには3種類ある(ローカル、ウェルノウン、グローバル)。
- ローカルは
Symbol()
関数でつくる。 - ウェルノウンは
Symbol.iterator
みたいに使う。 - グローバルは
Symbol.key()
メソッドで作る。