JavaScript for impatient programmers (ES2022 edition)
この本のサポートをお願いします: 購入する または 寄付する
(広告です。ブロックしないでください。)

22 シンボル



22.1 シンボルはプリミティブ値であり、オブジェクトのような側面も持つ

シンボルは、ファクトリ関数 Symbol() を介して作成されるプリミティブ値です。

const mySymbol = Symbol('mySymbol');

パラメータはオプションで、説明を提供します。これは主にデバッグに役立ちます。

22.1.1 シンボルはプリミティブ値である

シンボルはプリミティブ値です。

22.1.2 シンボルはオブジェクトのような側面も持つ

シンボルはプリミティブ値ですが、Symbol() によって作成された各値は一意であり、値によって比較されないという点で、オブジェクトのような側面も持ちます。

> Symbol() === Symbol()
false

シンボルが登場する前は、一意な(自分自身とのみ等しい)値が必要な場合、オブジェクトが最良の選択肢でした。

const string1 = 'abc';
const string2 = 'abc';
assert.equal(
  string1 === string2, true); // not unique

const object1 = {};
const object2 = {};
assert.equal(
  object1 === object2, false); // unique

const symbol1 = Symbol();
const symbol2 = Symbol();
assert.equal(
  symbol1 === symbol2, false); // unique

22.2 シンボルの説明

シンボルファクトリ関数に渡すパラメータは、作成されたシンボルの説明を提供します。

const mySymbol = Symbol('mySymbol');

説明には、2つの方法でアクセスできます。

まず、.toString() によって返される文字列の一部です。

assert.equal(mySymbol.toString(), 'Symbol(mySymbol)');

次に、ES2019以降、プロパティ .description を介して説明を取得できます。

assert.equal(mySymbol.description, 'mySymbol');

22.3 シンボルのユースケース

シンボルの主なユースケースは次のとおりです。

22.3.1 定数の値としてのシンボル

赤、オレンジ、黄、緑、青、紫の色を表す定数を作成したいとします。簡単な方法の1つは、文字列を使用することです。

const COLOR_BLUE = 'Blue';

プラス面としては、その定数をログに記録すると、役立つ出力が生成されます。マイナス面としては、内容が同じ2つの文字列は等しいと見なされるため、無関係な値を色と間違えるリスクがあります。

const MOOD_BLUE = 'Blue';
assert.equal(COLOR_BLUE, MOOD_BLUE);

この問題は、シンボルを使用して解決できます。

const COLOR_BLUE = Symbol('Blue');
const MOOD_BLUE = Symbol('Blue');

assert.notEqual(COLOR_BLUE, MOOD_BLUE);

シンボル値の定数を使用して関数を実装してみましょう。

const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

function getComplement(color) {
  switch (color) {
    case COLOR_RED:
      return COLOR_GREEN;
    case COLOR_ORANGE:
      return COLOR_BLUE;
    case COLOR_YELLOW:
      return COLOR_VIOLET;
    case COLOR_GREEN:
      return COLOR_RED;
    case COLOR_BLUE:
      return COLOR_ORANGE;
    case COLOR_VIOLET:
      return COLOR_YELLOW;
    default:
      throw new Exception('Unknown color: '+color);
  }
}
assert.equal(getComplement(COLOR_YELLOW), COLOR_VIOLET);

22.3.2 一意なプロパティキーとしてのシンボル

オブジェクトのプロパティ(フィールド)のキーは、2つのレベルで使用されます。

プログラムの基本レベルとメタレベルは独立している必要があります。基本レベルのプロパティキーは、メタレベルのプロパティキーと競合してはなりません。

名前(文字列)をプロパティキーとして使用する場合、2つの課題に直面します。

これらは、後者がJavaScriptの問題であった2つの例です。

プロパティキーとして使用されるシンボルは、ここで役立ちます。各シンボルは一意であり、シンボルキーは他の文字列キーまたはシンボルキーと競合することはありません。

22.3.2.1 例: メタレベルメソッドを持つライブラリ

例として、特別なメソッドを実装しているオブジェクトを異なる方法で扱うライブラリを作成しているとします。これは、このようなメソッドのプロパティキーを定義し、オブジェクトに対して実装する方法です。

const specialMethod = Symbol('specialMethod');
const obj = {
  _id: 'kf12oi',
  [specialMethod]() { // (A)
    return this._id;
  }
};
assert.equal(obj[specialMethod](), 'kf12oi');

行Aの大括弧を使用すると、メソッドがキー specialMethod を持つ必要があることを指定できます。詳細は、§28.7.2「オブジェクトリテラルの計算されたキー」で説明しています。

22.4 公開されているシンボル

ECMAScript内で特別な役割を果たすシンボルは、*公開されているシンボル*と呼ばれます。例としては、

  演習: 公開されているシンボル

22.5 シンボルの変換

シンボル sym を別のプリミティブ型に変換するとどうなりますか?表 15 に答えがあります。

表15: シンボルを他のプリミティブ型に変換した結果。
変換先 明示的な変換 型強制(暗黙的な変換)
真偽値 Boolean(sym) OK !sym OK
数値 Number(sym) TypeError sym*2 TypeError
文字列 String(sym) OK ''+sym TypeError
sym.toString() OK `${sym}` `→` `TypeError`

シンボルに関する重要な落とし穴の1つは、シンボルを他の型に変換しようとすると、例外がスローされる頻度が高いことです。その背後にある考え方は何でしょうか?第一に、数値への変換は意味がなく、警告が表示されるべきです。第二に、シンボルを文字列に変換することは、診断出力に役立ちます。しかし、シンボルを誤って文字列(別の種類のプロパティキー)に変換することについても警告することも理にかなっています。

const obj = {};
const sym = Symbol();
assert.throws(
  () => { obj['__'+sym+'__'] = true },
  { message: 'Cannot convert a Symbol value to a string' });

欠点は、例外によってシンボルの扱いが複雑になることです。プラス演算子を使用して文字列を組み立てるときは、シンボルを明示的に変換する必要があります。

> const mySymbol = Symbol('mySymbol');
> 'Symbol I used: ' + mySymbol
TypeError: Cannot convert a Symbol value to a string
> 'Symbol I used: ' + String(mySymbol)
'Symbol I used: Symbol(mySymbol)'

  クイズ

クイズアプリを参照してください。