JavaScript | ES6+ 文法:その他の ES6+ 機能 – Symbol

JavaScript JavaScript
スポンサーリンク

Symbol とは何か(まずイメージから)

Symbol は、ES6 で追加された 「絶対にかぶらない“名前札”のような値」 です。
stringnumber と同じ「プリミティブ型」の一種ですが、役割がかなり特殊です。

一番大事な性質は「毎回必ず一意(ユニーク)になる」ことです。

const a = Symbol("id");
const b = Symbol("id");

console.log(a === b); // false
JavaScript

同じ説明文 "id" を付けても、ab は絶対に一致しません。

ここが重要です。
文字列キーだと「同じ文字列なら同じキー」ですが、Symbol は「見た目が同じ説明文でも中身は別物」。
このおかげで、オブジェクトのプロパティ名が他と衝突しない“秘密のキー”を作れます。


Symbol の作り方と基本的な性質

Symbol の生成(constructor ではなく関数呼び出し)

Symbol は new を付けずに Symbol() 関数で作ります。

const sym1 = Symbol();          // 説明なし
const sym2 = Symbol("userId");  // 説明つき

console.log(sym1);              // Symbol()
console.log(sym2);              // Symbol(userId)
JavaScript

カッコの中の "userId" は「説明(description)」であって、
「中身の値」ではありません。
説明はログやデバッグ用に表示されるだけで、「同じ説明でも別の Symbol」です。

毎回ユニーク、説明文が同じでも一致しない

const a = Symbol("id");
const b = Symbol("id");

console.log(a === b); // false
JavaScript

Symbol() を呼ぶたびに、内部的にまったく新しい一意の値が作られます。

この性質が、「名前衝突を避けるキー」としてめちゃくちゃ役立ちます。

文字列に自動変換されない(ちょっと特別扱い)

多くの値は、alert や文字列連結で勝手に文字列になりますが、
Symbol は「自動では文字列にならない」という特殊ルールを持っています。

const sym = Symbol("id");

// console.log("値は " + sym);  // TypeError: Cannot convert a Symbol value to a string

console.log("値は " + String(sym));     // OK: "値は Symbol(id)"
console.log("値は " + sym.toString());  // OK: "値は Symbol(id)"
JavaScript

これは「Symbol を意図せず文字列として扱ってしまうミス」を防ぐためです。
使いたいときは、String()toString() を明示的に使います。


なぜ Symbol が必要なのか(衝突しない“隠しプロパティ”)

文字列キーは簡単にかぶってしまう

例えばライブラリと自分のコードが、同じオブジェクトにプロパティを追加するとします。

const user = { name: "Alice" };

// ライブラリ側
user.id = 1;

// 自分のコード側
user.id = "xxx";  // うっかり上書き
JavaScript

両方とも "id" という文字列キーを使っているので、
互いの値を上書きしあってしまいます。

Symbol をキーにすると、絶対にかぶらない

Symbol をプロパティキーとして使うと、「完全に自分専用のキー」を作れます。

const ID = Symbol("id");

const user = { name: "Alice" };

user[ID] = 1;  // Symbol をキーにする

console.log(user.name); // "Alice"
console.log(user[ID]);  // 1
JavaScript

他のコードが同じ "id" という文字列キーを使っても、
この Symbol キーとは一切関係ありません。

user.id = "文字列の id"; // これは別のキー

console.log(user.id);   // "文字列の id"
console.log(user[ID]);  // 1(こちらは Symbol キー)
JavaScript

ここが重要です。
Symbol を使うと、「他人のコードと絶対に衝突しない専用プロパティ」を作れる。
これがライブラリ設計や大規模なコードでの大きな武器になります。

Symbol プロパティは普通の列挙に“出てこない”

Symbol キーで追加したプロパティは、for...inObject.keys では列挙されません。

const ID = Symbol("id");

const user = {
  name: "Alice",
  [ID]: 1,
};

console.log(Object.keys(user)); // ["name"] だけ
for (const key in user) {
  console.log(key);             // "name" だけ
}

console.log(Object.getOwnPropertySymbols(user)); 
// [ Symbol(id) ] が取れる
JavaScript

「通常のキー一覧には出てこないが、Symbol 用の API ではちゃんと取れる」という挙動です。

ここが重要です。
Symbol プロパティは「半分隠しプロパティ」のように扱える。
外からうっかり触られたり、シリアライズされたりしにくい、弱いカプセル化の手段になります。


グローバル Symbol レジストリと Symbol.for

Symbol() と Symbol.for() の違い

Symbol() は呼ぶたびにユニーク、
Symbol.for() は「同じキーなら同じ Symbol を返す」仕組みを持っています。

const a = Symbol("id");
const b = Symbol("id");
console.log(a === b); // false(毎回ユニーク)

const c = Symbol.for("id");
const d = Symbol.for("id");
console.log(c === d); // true(同じキーなら同じ Symbol)  [Mozilla Developer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
JavaScript

Symbol.for("id") を初めて呼ぶと、新しい Symbol がグローバルレジストリに登録され、
2 回目以降は同じものが返ってきます。

「アプリ全体(もしくは複数ファイル間)で共有したい Symbol」が欲しいときに使います。

Symbol.keyFor で逆引きできる

Symbol.for で作られた Symbol は、
Symbol.keyFor で「登録名」を逆引きできます。

const sym = Symbol.for("myKey");

console.log(Symbol.keyFor(sym)); // "myKey"
JavaScript

Symbol() で作った普通の Symbol は、この逆引きができません。

const localSym = Symbol("local");
console.log(Symbol.keyFor(localSym)); // undefined
JavaScript

実務での具体的な使いどころ

オブジェクトに「ライブラリ用のメタ情報」を仕込みたい

他人のオブジェクトに、自分のライブラリ用の情報をこっそり付けたいとします。

const META = Symbol("meta");

function attachMeta(obj, meta) {
  obj[META] = meta;
}

function getMeta(obj) {
  return obj[META];
}

const user = { name: "Alice" };
attachMeta(user, { loadedAt: Date.now() });

console.log(user.name);      // "Alice"
console.log(getMeta(user));  // { loadedAt: ... }
JavaScript

この META プロパティは

  • 普通の for...inObject.keys には出てこない
  • 他のコードが "meta" という文字列キーを使っても、衝突しない

という性質を持ちます。
まさに「裏側でこっそり動くフラグ」用のキーとしてぴったりです。

列挙して欲しくない内部用フラグを持ちたい

const IS_ADMIN = Symbol("isAdmin");

const user = {
  name: "Alice",
  [IS_ADMIN]: true,
};

console.log(Object.keys(user)); // ["name"]
console.log(user[IS_ADMIN]);    // true
JavaScript

外から Object.keys でプロパティを全部見ても、
IS_ADMIN は見えません。
内部実装としてフラグを持ちたいけれど、
「通常のプロパティ一覧には出したくない」場合に Symbol は便利です。


Symbol 自体を理解するためのポイント整理

「プリミティブ型」だが、リテラル表現がない

数値なら 123、文字列なら "abc" というリテラルがありますが、
Symbol にはリテラルがなく、必ず Symbol() / Symbol.for() 経由で作ります。

const sym = Symbol("id"); // こうするしかない
JavaScript

一致判定は「同じインスタンスかどうか」

const s1 = Symbol("x");
const s2 = Symbol("x");

console.log(s1 === s2); // false
JavaScript

説明文はただのラベルであって、
=== の比較には一切関係しません。

JSON にはそのまま入らない(シリアライズ対象外になることが多い)

JSON.stringify は Symbol キーのプロパティを無視します。
「ネットワークでやり取りするデータ」には基本的に登場しないので、
Symbol は「コードの中だけで使うメタ情報」のイメージが強いです。


まとめ

Symbol の核心は、
「絶対にかぶらないユニークな“名前札”を作り、その名前をオブジェクトの隠れたキーとして使える」
ことにあります。

押さえておきたいポイントは次の通りです。

  • Symbol は ES6 で追加されたプリミティブ型で、Symbol() を呼ぶたびに必ずユニークな値が生成される
  • 説明文(description)はログ用ラベルであって、同じ説明でも Symbol 同士は絶対に一致しない
  • Symbol をプロパティキーにすると、文字列キーと衝突しない“秘密のプロパティ”を作れる
  • Symbol プロパティは for...inObject.keys では列挙されず、Object.getOwnPropertySymbols などでのみ取得できる
  • Symbol.for / Symbol.keyFor を使うと、グローバルレジストリを介して「名前付きの共有 Symbol」を扱える

初心者向けの距離感としては、
「普段はあまり使わないけれど、“衝突しない隠しキー”が必要になったときに思い出す存在」
くらいで十分です。

まずは小さなオブジェクトに対して、

  • 文字列キーで普通のプロパティを追加する
  • Symbol キーで「隠しプロパティ」を追加する

というサンプルを書いてみて、Object.keysfor...in の結果を比べてみてください。
その瞬間、Symbol の「ちょっと裏側にいる特別な存在感」が、かなりはっきり掴めるはずです。

タイトルとURLをコピーしました