Symbol とは何か(まずイメージから)
Symbol は、ES6 で追加された 「絶対にかぶらない“名前札”のような値」 です。string や number と同じ「プリミティブ型」の一種ですが、役割がかなり特殊です。
一番大事な性質は「毎回必ず一意(ユニーク)になる」ことです。
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b); // false
JavaScript同じ説明文 "id" を付けても、a と b は絶対に一致しません。
ここが重要です。
文字列キーだと「同じ文字列なら同じキー」ですが、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
JavaScriptSymbol() を呼ぶたびに、内部的にまったく新しい一意の値が作られます。
この性質が、「名前衝突を避けるキー」としてめちゃくちゃ役立ちます。
文字列に自動変換されない(ちょっと特別扱い)
多くの値は、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...in や Object.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)
JavaScriptSymbol.for("id") を初めて呼ぶと、新しい Symbol がグローバルレジストリに登録され、
2 回目以降は同じものが返ってきます。
「アプリ全体(もしくは複数ファイル間)で共有したい Symbol」が欲しいときに使います。
Symbol.keyFor で逆引きできる
Symbol.for で作られた Symbol は、Symbol.keyFor で「登録名」を逆引きできます。
const sym = Symbol.for("myKey");
console.log(Symbol.keyFor(sym)); // "myKey"
JavaScriptSymbol() で作った普通の 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...inやObject.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...inやObject.keysでは列挙されず、Object.getOwnPropertySymbolsなどでのみ取得できる Symbol.for/Symbol.keyForを使うと、グローバルレジストリを介して「名前付きの共有 Symbol」を扱える
初心者向けの距離感としては、
「普段はあまり使わないけれど、“衝突しない隠しキー”が必要になったときに思い出す存在」
くらいで十分です。
まずは小さなオブジェクトに対して、
- 文字列キーで普通のプロパティを追加する
- Symbol キーで「隠しプロパティ」を追加する
というサンプルを書いてみて、Object.keys や for...in の結果を比べてみてください。
その瞬間、Symbol の「ちょっと裏側にいる特別な存在感」が、かなりはっきり掴めるはずです。
