C
JavaScript/上級/Lesson 20

プロトタイプ — *JS のオブジェクト指向*

45分·theory
このチャプター
2/2

プロトタイプ — *JS のオブジェクト指向*

🎯 このレッスンを読み終えたら

このレッスンを読み終えると、以下の3つを自信を持って説明できるようになります。

  • ✅ prototype chain + __proto__ の仕組み
  • class がプロトタイプの syntactic sugar である理由
  • ✅ Object.create と new キーワードの比較

学習目標をチェックリストとして手元に置き、すべて答えられるようになったらレッスンを閉じてください。

プロトタイプとは何か

核心の一言

JS のオブジェクト指向はクラスベースではなくプロトタイプベース。すべてのオブジェクトは親オブジェクト (プロトタイプ) を持ち、そこからメソッドを継承します。

他の言語との違い

python
# Python (クラスベース)
class Animal:
    def speak(self): print("鳴き声")

class Dog(Animal):
    pass

d = Dog()
d.speak()   # "鳴き声" (Animal から継承)
javascript
// JS (プロトタイプベース)
const animal = {
    speak() { console.log("鳴き声"); }
};

const dog = Object.create(animal);   // animal をプロトタイプに
dog.speak();   // "鳴き声" (animal で発見)

オブジェクトが別のオブジェクトを直接継承 — クラスというテンプレートの段階はありません。

プロトタイプチェーン

javascript
const a = { x: 1 };               // 最上位の親 — x を保有
const b = Object.create(a);        // b の親 = a
const c = Object.create(b);        // c の親 = b

console.log(c.x);   // 1
//
// 🔎 c.x はどうやって見つかったか? 上に向かって探索:
//   ① c 自身 → x なし
//   ② c の親 b → x なし
//   ③ b の親 a → x = 1 発見! ✅
//
// 💡 これが「プロトタイプチェーン」— JS がプロパティを探す仕組み

プロパティを要求されると、JS は上に向かって探索します。これがプロトタイプチェーンです。

class 構文 — 見た目だけ

ES6 (2015) から class キーワードが使えますが、内部はいまもプロトタイプ

javascript
class Animal {
    speak() { console.log("音"); }
}

class Dog extends Animal {        // Animal を継承
    bark() { console.log("ワンワン"); }
}

const d = new Dog();
d.speak();   // "音"   ← Dog になし → Animal で発見
d.bark();    // "ワンワン"   ← Dog にあり → そのまま使用

クラスベース言語のスタイルで記述できます。しかし内部では:

javascript
Dog.prototype.__proto__ === Animal.prototype;   // true
d.__proto__ === Dog.prototype;                   // true

プロトタイプチェーンはそのままです。

よく見るプロトタイプのメソッド

javascript
const arr = [1, 2, 3];
// 🔎 arr の親は誰か?
console.log(arr.__proto__ === Array.prototype);   // true
console.log(arr.map(x => x * 2));                 // [2, 4, 6]
//             ↑ map は arr 自身ではなく Array.prototype に定義されている!

const s = "hello";
console.log(s.__proto__ === String.prototype);    // true
console.log(s.toUpperCase());                     // "HELLO"
//             ↑ toUpperCase も String.prototype に定義されて共有されている

// 💡 すべての配列・文字列がメソッドを「共有」→ 各インスタンスにコピー不要 → メモリ効率 ↑

すべての組み込みメソッド (map・filter・toUpperCase など) がプロトタイプに定義されており、すべての配列・文字列が共有しています。メモリ効率 ↑。

自分で作ってみる

javascript
function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(`${this.name}: 鳴き声`);
};

const a = new Animal("ワンちゃん");
a.speak();   // "ワンちゃん: 鳴き声"

古いコンストラクタ関数スタイルです。現在は class 構文の方がすっきりしているので、ほぼ常に class を使います。

よくある落とし穴

1. アロー関数にはプロトタイプがない:

javascript
const Foo = () => {};
new Foo();   // ❌ TypeError

2. __proto__ を直接操作するのは危険:

javascript
obj.__proto__ = anotherObj;   // 可能だがパフォーマンスが低下
// 代わりに Object.create() または Object.setPrototypeOf() を使う

3. for...in の意外な挙動:

javascript
for (const key in obj) { ... }
// 自身の + *継承した*プロパティをすべて反復
// 自身のプロパティだけ: Object.keys(obj) または obj.hasOwnProperty(key)

まとめ

  • JS = プロトタイプベースのオブジェクト指向
  • すべてのオブジェクトは親 (プロトタイプ) を持つ
  • classsyntactic sugar — 内部はプロトタイプ
  • 継承とメソッド共有のメカニズム

⚡ 実際に試してみよう — クラス継承 + プロトタイプチェーン

クラス継承が内部的にはプロトタイプチェーンであることを実際に確認してみましょう。
✏️ JS 코드
📟 コンソール出力
▶ 実行ボタンを押してください
⚠️ ブラウザのサンドボックスで実行 — console.log()のみ対応、alert/fetchは不可

🤖 AI にこう聞いてみよう

このレッスンの概念を知っていれば、AI に具体的に指示できます。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。

  • 「この変数に適切な TypeScript 型アノテーションを付けて」
  • 「この === と == の混在を === に統一して意図を明確にして」
  • 「このコードの JSON.parse 結果に unknown 型 + 型ガードを追加して」

なぜこれがトークンを減らすのか

概念を知らないと、AI の返答を受けても「それってどういう意味ですか?」と再度聞く必要があります。その「再質問」がトークンを消費します。概念を一度身につけておけば、会話が一度で終わります

プロトタイプ - JavaScript