Hello there, ('ω')ノ
💥 問題のコード:1行ずつ分解
まず、改めて「マージ関数」と「渡すデータ」を確認します:
🔁 マージ関数
function merge(target, source) { for (let key in source) { if (typeof source[key] === 'object') { if (!target[key]) target[key] = {}; merge(target[key], source[key]); } else { target[key] = source[key]; } } }
🧪 ユーザー入力(JSON)
{ "__proto__": { "isAdmin": true } }
🔎 ステップバイステップで追ってみよう!
呼び出しはこうなっています:
merge({}, { "__proto__": { "isAdmin": true } });
① for (let key in source)
sourceは{"__proto__": { "isAdmin": true }}。- ループで
key = "__proto__"が取り出されます。
② typeof source[key] === 'object'
source["__proto__"]は{ "isAdmin": true }というオブジェクトなのでtrue。- 次の処理へ進みます。
③ if (!target[key]) target[key] = {};
target["__proto__"]を確認。- 実は、ここで**
target["__proto__"]にアクセスすること自体が危険**です。
🔴 この行の副作用:
target["__proto__"] = {};
この時点で、Object.prototype を空のオブジェクト {} に書き換えてしまいます。
なぜなら __proto__ というキーは、JavaScriptにとって特別で、オブジェクトの「親」を指しているからです。
④ merge(target["__proto__"], source["__proto__"])
- 今度は
{}と{ "isAdmin": true }をまたmerge()に渡します。
⑤ 次のループ
key = "isAdmin"が取り出され、- 最後の
elseに行きます:
target[key] = source[key]; // → {}["isAdmin"] = true
- これにより、
Object.prototype.isAdmin = trueという状態が出来上がるわけです。
🧠 イメージ図
ステップ0: すべてのオブジェクト → Object.prototype → {}
↑
ステップ1: __proto__ を通じて Object.prototype を上書き開始
↓
ステップ2: isAdmin プロパティを追加(毒を盛る)
↓
ステップ3: 以後すべてのオブジェクトに isAdmin が見える!
✅ まとめ(初心者向けの理解ポイント)
| ポイント | 解説 |
|---|---|
__proto__ |
オブジェクトの「親(プロトタイプ)」を指す特別なキー |
target["__proto__"] = {...} |
実は「グローバルなObject.prototype」を書き換える操作になる |
| すべてのオブジェクト | Object.prototype を継承しているので、汚染の影響を受ける |
| 初心者が混乱する点 | target["__proto__"] を代入したつもりでも、実際はオブジェクト全体のプロトタイプを書き換えていること |
Best regards, (^^ゞ