Edited at

JavaScript の prototype の復習

More than 1 year has passed since last update.

ドワンゴ Advent Calendar の 5 日目の記事です。ドワンゴといえばカドカワ。カドカワといえば角川ゲームスの RPG ツクール MV。 RPG ツクール MV といえば JavaScript なので、今回は JavaScript に関する記事です。

RPG ツクール MV をいじって遊んでいたら、 prototype について案外勘違いしてしまっていたところがあったので、復習を兼ねて JavaScript の prototype についてメモします。ついでに RPG ツクール MV で使用されている継承イディオムについてもメモしています。なお、 ES6 以降では無論こういったイディオムを使わずに class を使うべきですが、その場合でも裏方の仕組みである prototype の仕組みについて知っておいて損はないでしょう。


__proto__prototype


  • オブジェクトには [[Prototype]] というプロトタイプチェーンに使われる内部スロットがある (9.1)。


    • プロトタイプチェーンは、オブジェクトのプロパティを探索する仕組みである (本稿では説明を割愛する)。




  • __proto__ は内部メソッド [[GetPrototypeOf]] および [[SetPrototypeOf]] を通じて [[Prototype]] を暴露するプロパティである (9.1, B.2.2.1)。


  • __proto__ は ES6 で標準化されたものの、表立って使用することは望ましくない。 __proto__ に何かを突っ込みたい場合は、以下に説明する new 演算子や Object.create を使用することが望ましい。


  • new 演算子のオブジェクト生成時に __proto__ にセットされる値には、コンストラクタ関数の prototype が使用される (6.1.7.2, 12.3.3)。


new 演算子

function Foo() {}

var foo = new Foo();

は基本的に以下と等価。

function Foo() {}

var foo = {};
foo.__proto__ = Foo.prototype; // プロトタイプオブジェクトの設定
Foo.apply(foo); // foo を this として Foo 関数を呼ぶ


constructor プロパティ

function Foo() {}

var foo = new Foo();

// foo.constructor という呼び出しは foo.__proto__.constructor の呼び出しになる。
// foo 自身は constructor というプロパティを持っていない。
console.log(foo.hasOwnProperty('constructor')); // false
console.log(foo.__proto__.hasOwnProperty('constructor')); // true
console.log(Foo.prototype.hasOwnProperty('constructor')); // true

// 以下はすべて等価
console.log(foo.constructor === Foo) // true
console.log(foo.__proto__.constructor === Foo) // true
console.log(Foo.prototype.constructor === Foo) // true


Object.create 関数

Object.create は、引数をプロトタイプ (__proto__) に持つオブジェクトを生成する (4.3.5, 19.1.2.2)。

function Foo {};

var foo = Object.create(Foo.prototype);

は基本的に以下と等価。

function Foo {};

var foo = {};
foo.__proto__ = Foo.prototype;

誤解を恐れずに言うと、「コンストラクタを実行しない new」である。


継承


古いやり方

function Foo() {}

function Bar() {}
Bar.prototype = new Foo();

一見よさそうに見えるが、これには問題がある。 Bar のインスタンスごとに Foo のインスタンス (Foo.prototype__proto__ に持つオブジェクト) がほしいが、この継承の時点ではコンストラクタ Foo を実行してほしくない。そのために Object.create を使う。


Object.create を使うやり方

function Foo() {}

function Bar() {}
Bar.prototype = Object.create(Foo.prototype);
// この時点だと Bar.prototype.constuctor は Foo なので、 Bar に直す必要がある。
Bar.prototype.constructor = Bar;

サイの表紙で有名な「サイ本」やいま巷で話題の RPG ツクール MV はこの方式。 Bar を以下のようにすることで親クラスコンストラクタを呼ぶことができる。

function Bar() {

Foo.apply(this, arguments);
}


参考