JavaScript | 第12章「オブジェクトに関する扱い方」

javascrpit JavaScript
スポンサーリンク

プロトタイプ(プロトタイプチェーン)と継承を「初心者向け」に噛み砕いて、たっぷり例付きで解説する。ポイントを押さえて、実際に試せるコードも入れるから手を動かしてみて。

プロトタイプと継承 — 概要(超かんたん)

  • オブジェクトは「プロトタイプ(親オブジェクト)」を持てる。
    あるオブジェクトにプロパティやメソッドが無いときは、その親(プロトタイプ)を順に探していく仕組みが プロトタイプチェーン
  • この仕組みによって 「同じ機能を何度も書かずに共有できる」=継承 が実現される。

例えると:部屋(オブジェクト)に椅子(プロパティ)が無ければ、隣の倉庫(プロトタイプ)を見に行って借りる感じ。

プロトタイプチェーン探索のアニメーション(親から順に探す様子)

なぜ大事?(メリット)

  • メモリを節約できる(共通メソッドをひとつだけ持たせる)
  • オブジェクトの振る舞い(メソッド)をまとめて管理できる(変更は親だけ)

実際にどう動くか:簡単なコードで理解する

const parent = {
  greet() {
    console.log("hello from parent");
  }
};

const child = Object.create(parent);
child.name = "child";

child.greet(); // "hello from parent"
JavaScript

動き:

  1. child.greet を呼ぶ
  2. childgreet が無い → 次に child のプロトタイプ(ここでは parent)を探す
  3. parentgreet がある → それを使う

Object.create(parent) は「parent をプロトタイプにした新しいオブジェクト」を作る便利な方法。

prototype と [[Prototype]] の違い(混乱しやすいポイント)

  • 関数オブジェクト(コンストラクタ関数)には .prototype プロパティがある。
    例:function Person(){}Person.prototypenew Person() が参照する「プロトタイプの設計図」。
  • 作られたオブジェクト自体は内部スロット [[Prototype]](多くの環境で __proto__ として見える)を持つ。これは実行時に次に探索する「親オブジェクト」を指す。

図(イメージ):

Person (function)
  └─ .prototype  ---> { sayHi: function() {...} }

alice (object) --[[Prototype]]--> Person.prototype
JavaScript

コード例(コンストラクタ関数と new):

function Person(name) {
  this.name = name;
}
Person.prototype.sayName = function() {
  console.log(this.name);
};

const alice = new Person("Alice");
alice.sayName(); // "Alice"
JavaScript
  • new Person("Alice") は内部で Person.prototypealice のプロトタイプにセットする。

instanceof とプロトタイプ

obj instanceof Constructor は、Constructor.prototypeobj のプロトタイプチェーンのどこかに存在するかをチェックする。

alice instanceof Person; // true
JavaScript

hasOwnProperty とプロトタイプ由来のプロパティの見分け方

  • obj.prop でプロパティが見つかっても、それが自分自身(自身のプロパティ)か、プロトタイプから来たものかは別。
  • 自身のプロパティか確認するには:
alice.hasOwnProperty("name");    // true
alice.hasOwnProperty("sayName"); // false(sayName は prototype 側)
JavaScript

プロパティの探索(シャドウイング)

  • 子オブジェクトに同じ名前のプロパティがあると、プロトタイプ側の同名プロパティは見えなく(上書きされて)なる。これをシャドウイング(shadowing)という。
Person.prototype.age = 0;
alice.age; // 0  (proto の値)
alice.age = 30; // 子に age を作る(シャドウ)
alice.age; // 30
// Person.prototype.age は依然 0 のまま
JavaScript

Object.create vs コンストラクタ関数(使い分け)

  • Object.create(proto):プロトタイプを直接指定して新しいオブジェクトを作る(シンプル・明示的)。
  • コンストラクタ関数 + new:オブジェクトに初期化処理(プロパティの設定)を組み込める。クラス構文の裏で使われる考え方。

どちらもプロトタイプ継承を使う手段だけど、最近は読みやすさのために ES6 の class 構文を使うことが多い(下で説明)。

ES6 の class 構文(見た目はクラスだがプロトタイプを使う)

class はプロトタイプ継承の「シンタックスシュガー(書きやすくしただけ)」で、内部的にはプロトタイプを使っている。

class Person {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log(this.name);
  }
}

const bob = new Person("Bob");
bob.sayName(); // "Bob"
JavaScript

Person.prototype.sayName に相当するメソッドは class のメソッドとして定義され、全インスタンスで共有される。

よくある誤解・注意点(初心者がつまずきやすい)

  1. プロパティを書き換えると全インスタンスに影響が出る?
    • prototype にある オブジェクト を直接変更すると、参照しているすべてのインスタンスに影響する(共有参照の副作用)。
    • 例:Person.prototype.tags = [] とすると全インスタンスで同じ配列を共有する → 予期せぬデータ混入に注意。
  2. this の値は呼び出し方で決まる
    • obj.method() のときは this === obj。でも const f = obj.method; f(); のように取り出すと thisundefined(strict mode)や global に変わる。
    • bind, call, applythis を明示的に固定できる。
  3. for...in はプロトタイプ上のプロパティも列挙する
    • for...in は列挙可能なプロパティをプロトタイプチェーン含めて列挙するので、通常は hasOwnProperty と組み合わせて使う。
  4. プロトタイプチェーンは実行時に辿られる(動的)
    • つまり、後から someProto.newMethod = ... と追加すれば、既に存在するインスタンスからも参照可能になる。

まとめ

  • 継承 = オブジェクトが別のオブジェクトを「親」にしてプロパティ/メソッドを借りる仕組み
  • JavaScript の継承は プロトタイプチェーン による(クラス構文はそのラッパー)。
  • 使うときは「どこにプロパティを置くか(自分側か prototype 側か)」で動作が大きく変わることを意識して。
  • 練習(3問 — 手を動かそう)
  1. const a = {x:1}; const b = Object.create(a); console.log(b.x); の出力は?(答え: 1
  2. コンストラクタ関数で greet メソッドを全インスタンスで共有する書き方を実装してみて。
  3. Person.prototype.arr = [] を定義して、p1.arr.push(1) したとき p2.arr はどうなる? なぜ?
タイトルとURLをコピーしました