ツクールの「おまじない」からの卒業（prototype編）

**副題【prototypeベースでの継承とprototypeチェーンをきちんと理解してから年を越そうぜ】**

【目次】
  
- 前置き
- prototypeによるウインドウクラスの継承
- 実例：Window_Scrollableの「おまじない」
  - おまじないの全体像
  - まずは定義－prototypeを親から引き継ぐ
  - インスタンス生成－`new`してみよう
  - 最後にinitializeが仕事をしてインスタンスの出来上がり
  - 「おまじない」のフローまとめ
- prototypeチェーンについて
- 終わりに

##前置き
JavaScript初級者がツクールMV、MZのプラグインを書いていると
「毎回なんとなく書いてはいるけれど、具体的にこれが何なのかはきちんと説明できない」
という**「おまじない」**のような記述が幾つか出てくるかと思います。

典型的なものとしては「プラグインコマンド関連処理」「プラグインパラメータ関連処理」、そして「新規シーンクラスや新規ウインドウクラスの定義」などです。
確かにこれら「おまじない」を「おまじない」のまま見様見真似で書いても動いてはくれますが、きちんと仕組みを理解しておくことで応用できる範囲が大きく広がり、また、自身でコアスクリプトを読み解く力が格段に上がります。

プラグインコマンドについては[前回](https://qiita.com/geregeregere/items/d87c9d0217bf359afb3d "RPGツクールMZでイベントコマンド「スクリプト」からプラグインコマンドを呼び出す方法")記事にしましたので、今回は新規ウインドウ作成を例としたprototypeベースの継承について書いてみます。

なお、本記事は基本情報としてmdnの
[「Object のプロトタイプ」](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Objects/Object_prototypes "Object のプロトタイプ")
[「継承とプロトタイプチェーン」](https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain "継承とプロトタイプチェーン")
を下地として記述しています。

##prototypeによるウインドウクラスの継承
ES20XX（以前はES6などとも呼ばれていた）以降はJavaScriptにもclass構文が導入されましたが、基本的な仕組みはprototypeベースのままです。
…というようなprototype概論は既にあちこちで記事にされていますし、なにより[mdnに詳細な説明ページ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain "継承とプロトタイプチェーン")が用意されていますので、その辺の詳しい説明はそちらに譲ります。
代えて、本記事ではツクールMZのコアスクリプトから例を取り、そこで何が行われているのかを順に追うことによってprototypeへの理解を深めることを試みます。

##実例：Window_Scrollableの「おまじない」
###おまじないの全体像
ツクールMZのウインドウは、元をたどれば全てWindow_Baseを継承して作られています。
今回はこのWindow_Baseを継承しているウインドウクラスの中から「Window_Scrollable」を例として追ってみましょう。
該当コードは以下です。

```JavaScript:rmmz_windows.js
function Window_Scrollable() {
    this.initialize(...arguments);
}

Window_Scrollable.prototype = Object.create(Window_Base.prototype);
Window_Scrollable.prototype.constructor = Window_Scrollable;

Window_Scrollable.prototype.initialize = function(rect) {
    Window_Base.prototype.initialize.call(this, rect);
    this._scrollX = 0;
    this._scrollY = 0;
    this._scrollBaseX = 0;
    this._scrollBaseY = 0;
    this.clearScrollStatus();
};
```

以下の４つの要素から構成されていることがわかりますね。
(1)`function`による関数の宣言
(2)`prototype`への代入
(3)`prototype.constructor`への代入
(4)`initialize`の定義

###まずは定義－prototypeを親から引き継ぐ
まず(1)で`Window_Scrollable`関数が宣言されています。この関数が後々`new`演算子によってインスタンスを作り出す元になります。
このように`new`演算子によってインスタンスを作り出す関数を「コンストラクター関数」と呼びます。
さて、ここでは(1)の中身にまでは踏み込まず、(2)および(3)から見ていきます。
いきなりですが、(2)がprototypeベースによる継承の肝となる部分です。

```JavaScript:
Window_Scrollable.prototype = Object.create(Window_Base.prototype);
```
`Object.create()`で、`Window_Base.prototype`を`[[prototype]]`として持つ新しいオブジェクトを生成し、`Window_Scrollable`の`prototype`プロパティに代入しています。

`prototype`および`[[prototype]]`という紛らわしい名前が出てきましたが、ここでは
 ・`prototype`…「子に引き継がせたいメソッドの情報を詰め込んだオブジェクト」
 ・`[[prototype]]`…「親から引き継いだメソッドを使用するために親へとたどるための参照」
くらいに留めておきます。
この辺について詳しくは「prototypeチェーンについて」で後述します。
ひとまず、これにより親クラスの持つメソッドを子クラスでも利用可能となったと認識してください。

さて、続けて(3)についてもここで理解しておきましょう。

```JavaScript:
Window_Scrollable.prototype.constructor = Window_Scrollable;
```
prototypeの中には「そのインスタンスを生成した際のコンストラクター関数への参照」がプロパティとして保持されています。それが`prototype.constructor`プロパティです。
（参考リンク：[mdn「Object.prototype.constructor」](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor "Object.prototype.constructor")）

通常は自動的にコンストラクター関数そのものへの参照が設定されるため、手動で書き換える必要はありません。ただし、クラスを継承する場合は(2)の処理によって`prototype`が丸ごと親クラス（Window_Base）の情報に上書きされてしまうため、このままの状態で`new Window_Scrollable()`を行うと、実際のコンストラクター関数はWindow_Scrollableなのに`prototype.constructor`はWindow_Baseになるという齟齬が発生してしまいます。
そのため、上記齟齬が起こらないように`prototype.constructor`を改めてWindow_Scrollableに紐付けしなおしておきます。
実際には`prototype.constructor`に齟齬が発生していても問題ない場合も多いのですが、「念のため」みたいな処理です。

この辺についてより詳しく知りたい場合は[mdn「Object.prototype.constructor：オブジェクトのコンストラクターの変更」](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor#Changing_the_constructor_of_an_object "Object.prototype.constructor")をご参照ください。

（余談：このように、prototypeを作って渡して、それをまた本来のコンストラクタに書き換えて、というような不要に捏ね繰り回す辺りが、prototypeベースの記述はイケてないと言われる大きな要因な気がします。class構文が使える場面ならclass構文を使いたいですね）

###インスタンス生成－`new`してみよう
定義が終わったので、次にWindow_Scrollableの新規インスタンス作成時、つまりは`new Window_Scrollable();`が行われた際に実行される処理を追っていきます。

`new`演算子は新しいインスタンスを生成するおなじみの演算子ですが、もう一歩だけ中に踏み込んで見てみましょう。
`new`演算子を実行すると、処理の裏側では空っぽの新規オブジェクトが生成され、それに対しコンストラクターの`prototype`を`[[prototype]]`に設定、thisの設定、コンストラクター関数の実行、などを行ってくれています。
（参考リンク：[mdn「new演算子」](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new "new演算子")）

よって、`new Window_Scrollable();`を行うと、まずまっさらな新規オブジェクト"{}"を作成し、そのオブジェクトの`[[prototype]]`に`Window_Scrollable.prototype`への参照をセットします。これによりWindow_Scrollableの持つメソッドをインスタンスから利用できるようになるわけです。（これについても後の「prototypeチェーンについて」で詳述）
そして`new`で指定したコンストラクター関数`Window_Scrollable`が適用され、インスタンスの情報が作られていきます。

さて、これでようやく(1)の中身に踏み込むタイミングが来ました。

```JavaScript:
function Window_Scrollable() {
    this.initialize(...arguments);
}
```
とは言え、やっていることは結局`initialize`メソッドへの丸投げです。
受け取った引数を展開し、自身の`initialize`メソッドへ渡してWindow_Scrollable関数の仕事はおしまいです。

（※なお、[`arguments`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/arguments "arguments")や[スプレッド構文（...）](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax "スプレッド構文")については本記事の主旨から外れるためここでは説明しませんが、頻出する記述なので馴染の薄い方はここで押さえておくことをお勧めします。）

###最後にinitializeが仕事をしてインスタンスの出来上がり
Window_Scrollable関数から`initialize`メソッドが呼ばれました。
(4)の部分です。

```JavaScript:
Window_Scrollable.prototype.initialize = function(rect) {
    Window_Base.prototype.initialize.call(this, rect);
    this._scrollX = 0;
    this._scrollY = 0;
    this._scrollBaseX = 0;
    this._scrollBaseY = 0;
    this.clearScrollStatus();
};
```
後はこのWindow_Scrollableクラスでインスタンス生成時に行っておきたい処理を記述するだけですね。
通常は親クラスの初期化処理（`initialize`）を呼び出した上で独自のプロパティ追加やメソッド呼び出しを行う場合が多いです。
今回のWindow_Scrollableでも、まずはWindow_Base側の初期化処理（`initialize`）を行い、加えて自身で追加したいプロパティを追加、処理しておきたいメソッドなどを記述しています。
これでWindow_Scrollableクラスのインスタンスが作成されました。

###「おまじない」の実行内容まとめ

・まずは一連のコードが読み込まれ、定義される。
　この際、(1)で宣言された「子クラス」のコンストラクター関数に対し、(2)によって
　「親クラス」の`prototype`が引き継がれる。
　ただし`prototype.constructor`は親クラスのコンストラクター関数でなく子クラスの
　コンストラクター関数でないと齟齬が起こるので、(3)で`prototype.constructor`だけは
　子クラスのコンストラクター関数に繋ぎ変える。

・実処理。コード内のどこかで「子クラス」のインスタンスが`new`演算子によって生成される。
　この時、`new`演算子の働きにより新規オブジェクトが生成され、その新規オブジェクトに対し
　コンストラクター関数(1)が実行される。更に新規オブジェクトの`[[prototype]]`に
　コンストラクター関数の`prototype`への参照が設定される。
　＞(1)の関数オブジェクトは(4)の`initialize`メソッドを呼び出すだけ。
　　(4)によって「子クラス」のインスタンスが初期化処理を行われてインスタンスの出来上がり。

こうして一連の流れを追ってみると、我々がクラスと呼んでいる物の正体はとどのつまり少し手の込んだFunctionオブジェクトに過ぎないことが見えて来ますね。

##prototypeチェーンについて
ここまでprototypeによるクラスの定義とインスタンス生成時の処理の流れを追ってきましたが、prototypeについて語る上では外せない要素がもう一つ残っています。
それはメソッドの**prototypeチェーン**です。

正確かつ詳細な説明については[mdn：「継承とプロトタイプチェーン」](https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain "継承とプロトタイプチェーン")を参照してもらうのが確実なのですが、ここではツクールMV/MZに即した説明を試みます。

まず登場人物を整理します。

`prototype`・・・子（インスタンス）で使用させたいメソッドやプロパティが詰め込まれている。
`[[prototype]]`・・・自身の親（コンストラクター）の`prototype`への参照が代入されている。
　　　　　　　　　　　 内部プロパティであり表面上は見えない（※）ようになっている。
（※余談だが、[getPrototypeOf](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf)で間接的にアクセスすることは可能）

名前が紛らわしいですが、この二つは明確に異なる物なのでしっかり区別しましょう。

さて、今回のWindow_Scrollableを例として、メソッドは何でもいいですが、仮にウインドウの行高さを得る`lineHeight`メソッドを使用したとします。
このメソッドは`Window_Base.prototype.lineHeight`として、Window_Baseの`prototype`プロパティに設定されたメソッドです。
この時、何が起こるのかを順に追ってみましょう。

```JavaScript:
const testWindow = new Window_Scrollable(rect);   //rectは事前に設定済とする
const lineHeight = testWindow.lineHeight();
```
この場合、それぞれの`prototype`および`[[prototype]]`は以下のようになります。

|   |自身が直接所有するプロパティ|prototype|[[prototype]]|
|:-:|:-:|:-:|:-:|
|Window_Base<br>（親クラスのコンストラクター）|`lineHeight`を持っていない|`lineHeight`を持っている<br>（ここで定義されている）|Functionオブジェクトの`prototype`<br>（今回の主題とはあまり関係なし）|
|Window_Scrollable<br>（子クラスのコンストラクター）|`lineHeight`を持っていない|`lineHeight`を持っていない|Functionオブジェクトの`prototype`<br>（今回の主題とはあまり関係なし）|
|testWindow<br>（子クラスのインスタンス）|`lineHeight`を持っていない|undefined|Window_Scrollable.prototype|

####(1) まずは自身が該当のプロパティ（メソッド）を持っていないかチェックする
　testWindowに対し`lineHeight`メソッドを呼び出すと、まず最初にtestWindowが`lineHeight`メソッドを持っていないか、つまり`testWindow.lineHeight`が存在するかどうかをチェックします。
ここでの「持っている/いない」というのは、**`[[prototype]]`を経由せずに、そのインスタンス（testWindow）自身に対し直接`lineHeight`というプロパティ（メソッド）が定義されているかどうか**、です。
チェックした結果、testWindowは`lineHeight`を直接には持っていませんので次に進みます。
####(2)次に`[[prototype]]`の中をチェックする
インスタンス（testWindow）が直接的に該当プロパティを持っていなければ、今度は`[[prototype]]`に`lineHeight`メソッドがないかを探します。
インスタンスの`[[prototype]]`には`Window_Scrollable.prototype`への参照が入っていましたね。
インスタンスの`[[prototype]]`（その参照先は`Window_Scrollable.prototype`）には`lineHeight`メソッドはまだ見つかりません。
次へと進みます。
####(3)`[[prototype]]`になければ、その`[[prototype]]`、そこにもなければ更にその…
`[[prototype]]`に該当プロパティが見つからなければ、次は`[[prototype]]`の`[[prototype]]`、つまり`testWindow.[[prototype]].[[prototype]]`をチェックします。
以下同様に、該当のメソッドが見つかるまで（あるいは見つからずにnullに辿り着くまで）延々と`[[prototype]]`を遡って行くことになります。

今回の場合、`Window_Scrollable.prototype`は(2)の処理によって`Object.create(Window_Base.prototype)`で新規生成されたオブジェクトが代入されています。
そのため`Window_Scrollable.prototype`の`[[prototype]]`は`Window_Base.prototype`であり、つまり`testWindow.[[prototype]].[[prototype]]`とは`Window_Base.prototype`を示しているわけです。

結果、`testWindow.lineHeight()`を行うと、`[[prototype]]`を頼りにWindow_Scrollable、次にWindow_Baseと順に`prototype`内を遡って行き、`Window_Base.prototype.lineHeight`を見つけることができました。

また、このように、遡って行くのはあくまでも`prototype`の中なので、仮に`Window_Scrollable.hogeMethod`のように`prototype`ではなくクラスに対し直接定義されていたメソッドは、インスタンスからは参照されません。
static（静的）メソッドがインスタンスから使用できない理由がこれです。


以上、ツクールMZのWindow_Selectableを例に流れを追ってみましたが、「余計にわかりにくいわー！」と感じた方は元ネタである[「継承とプロトタイプチェーン」](https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Inheritance_with_the_prototype_chain "継承とプロトタイプチェーン")の方を読んでみてください。
シンプルなオブジェクトを例にしてあるので、返ってそっちの方が分かりやすいかもしれません。

##終わりに
以上、JavaScriptのprototypeによる継承とprototypeチェーンについてでした。
これらを理解しているかいないかで、コアスクリプトのコードを読み解く速さと精度が大きく変わることかと思います。
脱・初級のためには早めに理解しておきたいポイントではないでしょうか。

では、今回もこの言葉で締めとさせていただきます。

**忌憚ないツッコミ待ちだぁ！ щ(ﾟДﾟщ) ﾊﾞｯﾁｺｰｲ**