Symbol
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since September 2015.
Symbol
は組み込みオブジェクトであり、コンストラクターは一意であることが保証されているシンボルプリミティブ(シンボル値または単にシンボル)を返します。シンボルは、他のコードがオブジェクトに追加する可能性のあるキーと衝突しないように、また、他のコードがオブジェクトにアクセスするために通常使用するメカニズムから隠されるるように、一意のプロパティキーをオブジェクトに追加するためによく使用されます。これによって弱いカプセル化、または弱い形の情報隠蔽が実現できます。
Symbol()
を呼び出すたびに、一意なシンボルを返すことが保証されています。すべての Symbol.for("key")
を呼び出すと、指定された "key"
の値に対して常に同じ Symbol を返します。Symbol.for("key")
が呼び出されると、与えられたキーを持つ Symbol がグローバルな Symbol レジストリーで見つかれば、その Symbol が返されます。そうでない場合は、新しい Symbol が作成され、与えられたキーの下でグローバル Symbol レジストリーに追加され、返されます。
解説
新しいプリミティブシンボルを作成するには、Symbol()
と記述し、その説明としてオプションの文字列を指定します。
const sym1 = Symbol();
const sym2 = Symbol("foo");
const sym3 = Symbol("foo");
上記のコードは 3 つの新しいシンボルを作成します。Symbol("foo")
は、文字列 "foo"
をシンボルに変換するわけではないことに注意してください。これは毎回新しいシンボルを作成します。
Symbol("foo") === Symbol("foo"); // false
次のように new
演算子を付けた構文では、TypeError
が発生します。
const sym = new Symbol(); // TypeError
これは、作者が新しいシンボル値の代わりに明示的な Symbol
ラッパーオブジェクトを作成することを防ぐものです。その他のプリミティブデータ型では、明示的なラッパーオブジェクトを作成することは一般的に可能なので(new Boolean
、new String
、new Number
など)、驚くかもしれません。
本当に Symbol
ラッパーオブジェクトを作成したい場合は、Object()
関数を使用してください。
const sym = Symbol("foo");
typeof sym; // "symbol"
const symObj = Object(sym);
typeof symObj; // "object"
シンボルは参照同一性を持つ唯一のプリミティブデータ型であるため(つまり、同じシンボルを 2 度作成することはできない)、ある意味ではオブジェクトのように振る舞います。例えば、シンボルはガベージコレクトできるので、WeakMap
、WeakSet
、WeakRef
、FinalizationRegistry
オブジェクトに格納できます。
グローバルシンボルレジストリー内の共有シンボル
上記の構文で Symbol()
関数を使用すると、プログラムのライフタイムを通じて値が一意であるシンボルが作成されます。ファイルをまたいで、さらにはレルム(それぞれが独自のグローバルスコープを持つ)をまたいで利用可能なシンボルを作成するには、Symbol.for()
と Symbol.keyFor()
メソッドを使用して、グローバルなシンボルレジストリーからシンボルを設定したり取得したりします。
なお、「グローバルシンボルレジストリー」は架空の概念に過ぎず、JavaScript エンジンの内部データ構造に対応するものがあるとは限りません。また、そのようなレジストリーが存在したとしても、その内容は for()
および keyFor()
メソッドを通さない限り、JavaScript のコードからは利用できません。
Symbol.for(tokenString)
メソッドは文字列のキーを取り、レジストリーからシンボル値を返すのに対し、Symbol.keyFor(symbolValue)
はシンボル値を受け取り、それに対応する文字列キーを返します。それぞれは逆の関係になるため、次の式は true
となります。
Symbol.keyFor(Symbol.for("tokenString")) === "tokenString"; // true
登録シンボルはどこでも任意に作成できるため、ラップする文字列とほとんど同じように動作します。そのため、一意であることは保証されず、ガベージコレクションの対象にもなりません。したがって、登録シンボルは WeakMap
, WeakSet
, WeakRef
, FinalizationRegistry
の各オブジェクトで使用することはできません。
ウェルノウンシンボル
Symbol
コンストラクターのすべての静的プロパティは、それ自身が領域をまたぐ定数の値を持ったシンボルです。これらは「ウェルノウンシンボル」と呼ばれています。これらは JavaScript の特定の組み込み操作の「プロトコル」として提供されているもので、ユーザーが言語の動作をカスタマイズすることができます。 例えば、コンストラクター関数が Symbol.hasInstance
という名前のメソッドを持っている場合、このメソッドは instanceof
演算子を使った動作をエンコードします。
ウェルノウンシンボルが導入される前、JavaScript では特定の組み込み操作を実装するために通常のプロパティを使用していました。例えば、JSON.stringify
関数は各オブジェクトの toJSON()
メソッドを呼び出そうとし、String
関数はオブジェクトの toString()
メソッドと valueOf()
メソッドを呼び出します。しかし、言語により多くの操作が追加されるにつれて、各操作を「マジックプロパティ」に指定することは、後方互換性を壊し、言語の動作を推論しにくくする可能性があることが分かってきました。ウェルノウンシンボルを使用することで、通常文字列プロパティしか読み取らない通常のコードから、カスタマイズを「見えない」ようにすることができます。
メモ:
仕様書では、ウェルノウンシンボルを表すために @@<シンボル名>
という表記を使っていました。例えば、Symbol.hasInstance
は @@hasInstance
と表記され、Array.prototype[Symbol.iterator]()
メソッドは Array.prototype[@@iterator]()
と呼ばれていました。この表記法は仕様書では使われなくなりましたが、古い文書や議論ではまだ見かけることがあります。
ウェルノウンシンボルはガベージコレクションの対象にはなりません。固定セットで提供され、Array.prototype
のような組み込みオブジェクトと同様に、プログラムの寿命を通じて一意であるためです。ですから、WeakMap
、WeakSet
、WeakRef
、FinalizationRegistry
の各オブジェクト内で使用することができます。
オブジェクトでのシンボルプロパティの検索
Object.getOwnPropertySymbols()
メソッドはシンボルの配列を返し、指定されたオブジェクトのシンボルプロパティを探すことができます。すべてのオブジェクトはシンボルプロパティを持たない状態で初期化されるため、オブジェクトにシンボルプロパティを設定しない限り、この配列は空になることに注意してください。
コンストラクター
Symbol()
-
シンボル型のプリミティブ値を返します。
new
を付けて呼び出すとエラーが発生します。
静的プロパティ
静的プロパティはすべてウェルノウンシンボルです。これらのシンボルの説明では、「Symbol.hasInstance
は ... を決定するメソッドです」というような言葉を使っていますが、これはオブジェクトのメソッドがこのシンボルをメソッド名として持つという意味であり(ウェルノウンシンボルは「プロトコル」として機能するため)、シンボルそのものの値を説明しているわけではないことを覚えておいてください。
Symbol.asyncIterator
-
オブジェクトの既定の非同期イテレーター (AsyncIterator) を返すメソッドです。
for await...of
から使用されます。 Symbol.hasInstance
-
コンストラクターオブジェクトがあるオブジェクトを自分のインスタンスとして認識するかどうかどうかを決定するメソッドです。
instanceof
から使用されます。 Symbol.isConcatSpreadable
-
論理値で、オブジェクトが配列要素に平坦化されるかどうかを示します。
Array.prototype.concat()
から使用されます。 Symbol.iterator
-
オブジェクトの既定のイテレーターを返すメソッドです。
for...of
から使用されます。 Symbol.match
-
文字列に対して照合するメソッドであり、オブジェクトが正規表現として使用できるかどうかを判断するためにも使用されます。
String.prototype.match()
から使用されます。 Symbol.matchAll
-
文字列に対して正規表現が一致したものを返すイテレーターを返すメソッドです。
String.prototype.matchAll()
から使用されます。 Symbol.replace
-
文字列の中で一致する部分文字列を置換するメソッドです。
String.prototype.replace()
から使用されます。 Symbol.search
-
文字列の中で正規表現に一致する部分のインデックスを返すメソッドです。
String.prototype.search()
から使用されます。 Symbol.species
-
派生オブジェクトを作成するために使用されるコンストラクター関数です。
Symbol.split
-
正規表現に一致するインデックスで文字列を分割するメソッドです。
String.prototype.split()
から使用されます。 Symbol.toPrimitive
-
オブジェクトをプリミティブ値に変換するメソッドです。
Symbol.toStringTag
-
オブジェクトの既定の説明に使用される文字列値です。
Object.prototype.toString()
から使用されます。 Symbol.unscopables
-
自身のプロパティ名および継承されたプロパティ名が、
with
環境の関連するオブジェクトのバインディングから除外されるオブジェクト値です。
静的メソッド
Symbol.for()
-
グローバルシンボルレジストリーから、与えられた
key
で既存の登録済みシンボルを検索し、見つかればそれを返します。見つからない場合は、新しいシンボルが作成され、key
で登録されます。 Symbol.keyFor()
-
指定されたシンボルに対して、グローバルシンボルレジストリーから共有シンボルキーを取得します。
インスタンスプロパティ
これらのプロパティは Symbol.prototype
で定義されており、すべての Symbol
インスタンスで共有されます。
Symbol.prototype.constructor
-
このインスタンスオブジェクトを作成したコンストラクター関数。
Symbol
インスタンスについては、初期値はSymbol
コンストラクターです。 Symbol.prototype.description
-
読み取り専用の文字列で、シンボルの説明が入ります。
Symbol.prototype[Symbol.toStringTag]
-
[Symbol.toStringTag]
プロパティの初期値は文字列"Symbol"
です。このプロパティはObject.prototype.toString()
で使用されます。ただし、Symbol
にも独自のtoString()
メソッドがあるため、シンボルをthisArg
としてObject.prototype.toString.call()
を呼び出さない限り、このプロパティは使用されません。
インスタンスメソッド
Symbol.prototype.toString()
-
文字列で、シンボルの説明を返します。
Object.prototype.toString()
メソッドをオーバーライドします。 Symbol.prototype.valueOf()
-
このシンボルを返します。
Object.prototype.valueOf()
ンメソッドをオーバーライドします。 Symbol.prototype[Symbol.toPrimitive]()
-
シンボルを返します。
例
シンボルに対する typeof 演算子の使用
typeof
演算子は、シンボルを識別するのに役立ちます。
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";
シンボルの型変換
シンボルの型変換を行う際に注意すべき点がいくつかあります。
- シンボルを数値に変換しようとすると、
TypeError
が発生します。 (例:+sym
やsym | 0
) - 緩い等価性を使用した場合、
Object(sym) == sym
はtrue
を返します。 Symbol("foo") + "bar"
ではTypeError
が発生します(シンボルは文字列に変換できません)。 これにより、例えば、シンボルから新しい文字列プロパティ名が暗黙に作成されることを防ぐことができます。- 「より安全な」
String(sym)
変換は、シンボルに対してSymbol.prototype.toString()
を呼び出すのと同様に動作しますが、new String(sym)
は例外が発生するので注意してください。
シンボルと for...in ループ
シンボルは for...in
ループでは列挙されません。 また、Object.getOwnPropertyNames()
はシンボルのオブジェクトプロパティを返しませんが、Object.getOwnPropertySymbols()
を使用して取得することは可能です。
const obj = {};
obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";
for (const i in obj) {
console.log(i);
}
// "c" "d"
シンボルと JSON.stringify()
シンボルをキーとするプロパティは、JSON.stringify()
を使用する際に完全に無視されます。
JSON.stringify({ [Symbol("foo")]: "foo" });
// '{}'
詳しくは JSON.stringify()
を参照してください。
Symbol ラッパーオブジェクトをプロパティのキーとして使用
Symbol ラッパーオブジェクトがプロパティキーとして使用された場合、このオブジェクトはラップされたシンボルに強制的に変換されます。
const sym = Symbol("foo");
const obj = { [sym]: 1 };
obj[sym]; // 1
obj[Object(sym)]; // still 1
仕様書
Specification |
---|
ECMAScript Language Specification # sec-symbol-objects |
ブラウザーの互換性
BCD tables only load in the browser
関連情報
Symbol
のポリフィル (core-js
)typeof
- JavaScript データ型とデータ構造
- ES6 In Depth: Symbols (hacks.mozilla.org, 2015)