すたらブログ

文系Webプログラマの備忘録

JavaScript: プロトタイプに初期値のつもりでオブジェクトを格納したら…

参照渡しされるもの(関数、オブジェクト、配列)をプロトタイプのプロパティに格納したらどうなるか。
よく理解していませんでした orz

私の行動

  1. プロトタイプのプロパティに、初期値のつもりでオブジェクトを格納した。
  2. そのオブジェクトのプロパティの数値を加算するメソッドを作った。
  3. インスタンスを複数作って、それぞれで加算メソッドを実行すると結果がおかしなことに。

ソースコード (JavaScript)

// クラス
function MyTest() {
    // インスタンス化の際に加算メソッドを実行
    this.add();
}

// 初期値(のつもり)
MyTest.prototype.obj = {foo: 0}; // オブジェクト(参照型)
MyTest.prototype.num = 0; // 数値(基本型)

// 加算メソッド
MyTest.prototype.add = function() {
    // 加算前の値(初期値のはず)を表示
    console.log(this.obj.foo + '/' + this.num);

    // それぞれ加算する
    this.obj.foo++;
    this.num++;

    // 加算後の値を表示
    console.log(this.obj.foo + '/' + this.num);
};

// インスタンスを複数作成
new MyTest();
new MyTest();

// 結果は下記のようになる
// 0/0
// 1/1
// 1/0
// 2/1

つまり、数値が入ってるプロパティは初期値として何の問題もないのに、 オブジェクトが入ってるプロパティは、別のインスタンスでの加算結果を引き継いでいます。

通常、インスタンスに存在しないメンバに対して、読み込みの場合はプロトタイプチェーンをたどって探します。
書き込みの場合はそのインスタンスに新たなプロパティが作成され、以後、そのプロパティが読み込まれます。

しかし、オブジェクトは参照渡しです。
おおもとのプロトタイプですら参照しか格納していないのですから、 読み込みでも書き込みでも、別々のインスタンスでも、 同一のオブジェクトを操作することになるのは当然です。

これに気づくまで、かなりの時間を費やしました (-_-;)

私の対処法

初期値としてオブジェクトや配列などの参照型を設定したい場合、コンストラクタの方に記述することにしました。

JavaScript
function MyTest() {
    // 参照型の初期値
    this.obj = {foo: 0};
    this.add();
}
// 基本型の初期値はプロトタイプでも問題なし
MyTest.prototype.num = 0;

プロトタイプだとか参照渡しだとか、基本であり重要な概念の理解がおろそかになってました orz

javascript プロトタイプ 初期値 参照型』でぐぐったら、多くの人がつまづく箇所なのかな、と思いました。