仕様により、オブジェクト プロパティ キーとして機能できるのは 2 つのプリミティブ型のみです。
文字列型、または
シンボルタイプ。
それ以外の場合、数値などの別の型を使用すると、文字列に自動変換されます。したがって、 obj[1]
obj["1"]
と同じであり、 obj[true]
obj["true"]
と同じです。
これまでは文字列のみを使用してきました。
次に、シンボルを調べて、シンボルが私たちに何ができるかを見てみましょう。
「シンボル」は一意の識別子を表します。
このタイプの値はSymbol()
使用して作成できます。
id = Symbol(); とします。
作成時に、シンボルに説明 (シンボル名とも呼ばれる) を付けることができます。これは主にデバッグ目的に役立ちます。
// id は「id」という説明を持つシンボルです let id = Symbol("id");
シンボルは一意であることが保証されます。まったく同じ説明を持つシンボルを多数作成したとしても、それらは異なる値になります。説明は単なるラベルであり、何も影響しません。
たとえば、ここに同じ説明を持つ 2 つのシンボルがありますが、これらは等しくありません。
id1 = Symbol("id"); とします。 id2 = Symbol("id"); とします。 アラート(id1 == id2); // 間違い
Ruby や、何らかの「シンボル」を持つ別の言語に精通している場合は、誤解しないでください。 JavaScript のシンボルは異なります。
したがって、要約すると、シンボルはオプションの説明を備えた「プリミティブな一意の値」です。どこで使用できるかを見てみましょう。
シンボルは文字列に自動変換されません
JavaScript のほとんどの値は、文字列への暗黙的な変換をサポートしています。たとえば、ほぼすべての値alert
ことができ、機能します。シンボルは特別です。自動変換されません。
たとえば、次のalert
はエラーが表示されます。
let id = Symbol("id"); アラート(ID); // TypeError: シンボル値を文字列に変換できません
これは、文字列と記号は根本的に異なるものであり、誤って別のものに変換してはいけないため、混乱を防ぐための「言語ガード」です。
本当にシンボルを表示したい場合は、次のように、明示的に.toString()
を呼び出す必要があります。
let id = Symbol("id"); アラート(id.toString()); // シンボル(id)、動作するようになりました
または、 symbol.description
プロパティを取得して説明のみを表示します。
let id = Symbol("id"); アラート(id.説明); // ID
シンボルを使用すると、コードの他の部分が誤ってアクセスしたり上書きしたりできない、オブジェクトの「隠し」プロパティを作成できます。
たとえば、サードパーティのコードに属するuser
オブジェクトを操作している場合です。それらに識別子を追加したいと思います。
それには記号キーを使用しましょう。
let user = { // 別のコードに属します 名前:「ジョン」 }; let id = Symbol("id"); ユーザー[id] = 1; アラート( ユーザー[id] ); // シンボルをキーとして使用してデータにアクセスできます
文字列"id"
ではなくSymbol("id")
を使用する利点は何ですか?
user
オブジェクトは別のコードベースに属しているため、フィールドを追加することは安全ではありません。他のコードベースで事前定義された動作に影響を与える可能性があるからです。ただし、シンボルに誤ってアクセスすることはできません。サードパーティのコードは新しく定義されたシンボルを認識しないため、 user
オブジェクトにシンボルを安全に追加できます。
また、別のスクリプトが独自の目的でuser
内に独自の識別子を持ちたいと考えていると想像してください。
次に、そのスクリプトは次のように独自のSymbol("id")
を作成できます。
// ... let id = Symbol("id"); user[id] = "その ID 値";
たとえ同じ名前であってもシンボルは常に異なるため、私たちの識別子とその識別子の間に競合は起こりません。
…しかし、同じ目的でシンボルの代わりに文字列"id"
使用すると、競合が発生します。
ユーザー = { 名前: "ジョン" }; // スクリプトでは「id」プロパティを使用します user.id = "私たちのID値"; // ...別のスクリプトもその目的のために「id」を必要としています... user.id = "そのID値" // ドーン!別のスクリプトによって上書きされます。
オブジェクト リテラル{...}
でシンボルを使用したい場合は、シンボルを角括弧で囲む必要があります。
このような:
let id = Symbol("id"); ユーザー = { にします 名前:「ジョン」、 [id]: 123 // 「id」ではありません: 123 };
これは、文字列「id」ではなく、変数id
の値をキーとして必要とするためです。
シンボリック プロパティはfor..in
ループには関与しません。
例えば:
let id = Symbol("id"); ユーザー = { にします 名前:「ジョン」、 年齢:30歳、 [ID]: 123 }; for (ユーザーにキーを入力させます)alert(key); // 名前、年齢(記号なし) // シンボルによる直接アクセスは機能します alert( "直接: " + ユーザー[id] ); // 直接: 123
Object.keys(user) もそれらを無視します。これは、一般的な「シンボル プロパティを隠す」原則の一部です。別のスクリプトまたはライブラリがオブジェクトをループしても、予期せずシンボリック プロパティにアクセスすることはありません。
対照的に、Object.assign は文字列とシンボルの両方のプロパティをコピーします。
let id = Symbol("id"); ユーザー = { にします [ID]: 123 }; let clone = Object.assign({}, user); アラート( クローン[id] ); // 123
ここには矛盾はありません。それは仕様によるものです。その考え方は、オブジェクトのクローンを作成するとき、またはオブジェクトをマージするときに、通常はすべてのプロパティ ( id
などのシンボルを含む) をコピーする必要があるということです。
これまで見てきたように、通常は、名前が同じであっても、すべてのシンボルは異なります。しかし、同じ名前のシンボルを同じエンティティにしたい場合もあります。たとえば、アプリケーションのさまざまな部分が、まったく同じプロパティを意味するシンボル"id"
にアクセスしたいとします。
これを実現するために、グローバル シンボル レジストリが存在します。その中にシンボルを作成し、後でアクセスすることができ、同じ名前で繰り返しアクセスしてもまったく同じシンボルが返されることが保証されます。
レジストリからシンボルを読み取る (存在しない場合は作成する) には、 Symbol.for(key)
を使用します。
この呼び出しはグローバル レジストリをチェックし、 key
として記述されたシンボルがある場合はそれを返し、そうでない場合は新しいシンボルSymbol(key)
を作成し、指定されたkey
によってレジストリに格納します。
例えば:
// グローバルレジストリから読み取ります let id = Symbol.for("id"); // シンボルが存在しなかった場合は作成されます // もう一度読みます (おそらくコードの別の部分から) idAgain = Symbol.for("id"); とします。 // 同じシンボル アラート( id === id再び ); // 真実
レジストリ内のシンボルは、グローバル シンボルと呼ばれます。コード内のどこからでもアクセスできる、アプリケーション全体のシンボルが必要な場合、それが目的です。
ルビーっぽいですね
Ruby などの一部のプログラミング言語では、名前ごとに 1 つのシンボルが存在します。
ご覧のとおり、JavaScript ではグローバル シンボルにも当てはまります。
グローバル シンボルの場合、 Symbol.for(key)
名前でシンボルを返すことがわかりました。逆に、グローバル シンボルで名前を返すには、 Symbol.keyFor(sym)
を使用します。
例えば:
// シンボルを名前で取得します sym = Symbol.for("名前"); とします。 sym2 = Symbol.for("id"); とします。 // シンボルで名前を取得 アラート( Symbol.keyFor(sym) ); // 名前 alert( Symbol.keyFor(sym2) ); // ID
Symbol.keyFor
内部でグローバル シンボル レジストリを使用してシンボルのキーを検索します。したがって、非大域シンボルに対しては機能しません。シンボルがグローバルでない場合は、シンボルを見つけることができず、 undefined
を返します。
つまり、すべてのシンボルにはdescription
プロパティがあります。
例えば:
let globalSymbol = Symbol.for("名前"); let localSymbol = Symbol("名前"); alert( Symbol.keyFor(globalSymbol) ); // 名前、グローバルシンボル alert( Symbol.keyFor(localSymbol) ); // 未定義、グローバルではない アラート( localSymbol.description ); // 名前
JavaScript が内部で使用する「システム」シンボルが多数存在し、それらを使用してオブジェクトのさまざまな側面を微調整できます。
それらは仕様の既知のシンボル表にリストされています。
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
…等々。
たとえば、 Symbol.toPrimitive
使用すると、オブジェクトからプリミティブへの変換を記述することができます。その使い方はすぐにわかります。
対応する言語の機能を学ぶと、他の記号もよく知られるようになります。
Symbol
、一意の識別子のプリミティブ タイプです。
シンボルは、オプションの説明 (名前) を指定してSymbol()
呼び出しを使用して作成されます。
シンボルは、同じ名前であっても、常に異なる値になります。同じ名前のシンボルを等しくしたい場合は、グローバル レジストリを使用する必要があります。 Symbol.for(key)
key
を名前とするグローバル シンボルを返します (必要に応じて作成します)。同じkey
を使用してSymbol.for
を複数回呼び出すと、まったく同じシンボルが返されます。
シンボルには 2 つの主な使用例があります。
「非表示」オブジェクトのプロパティ。
別のスクリプトまたはライブラリに「属する」オブジェクトにプロパティを追加したい場合は、シンボルを作成し、それをプロパティ キーとして使用できます。シンボリック プロパティはfor..in
には表示されないため、誤って他のプロパティと一緒に処理されることはありません。また、別のスクリプトにはシンボルがないため、直接アクセスすることはできません。したがって、プロパティは誤って使用されたり上書きされたりすることがなくなります。
したがって、記号プロパティを使用して、必要だが他の人には見られたくないオブジェクトの中に何かを「密かに」隠すことができます。
JavaScript で使用されるシステム シンボルは多数あり、 Symbol.*
としてアクセスできます。これらを使用して、組み込みの動作を変更できます。たとえば、チュートリアルの後半では、イテラブルにSymbol.iterator
使用し、オブジェクトからプリミティブへの変換をセットアップするためにSymbol.toPrimitive
使用します。
技術的には、シンボルは 100% 隠蔽されるわけではありません。すべてのシンボルを取得できる組み込みメソッド Object.getOwnPropertySymbols(obj) があります。また、シンボリックキーを含むオブジェクトのすべてのキーを返す Reflect.ownKeys(obj) というメソッドもあります。ただし、ほとんどのライブラリ、組み込み関数、および構文構造では、これらのメソッドは使用されません。