【Javascript】オブジェクトを new してもプロトタイプのプロパティは初期化されない件


Javascript でオブジェクトを new するとき、プロトタイプのプロパティは単にコピーされるだけで、初期化はされない。これに気づかずに一日無駄にしたorz 例を挙げると、

var MyObj = function(){};
MyObj.prototype = {
    a: function(){
        alert( this.b.join( " : " ) );
    }
       ,b: [ 1, 2 ]
};

var obj1 = new MyObj();
obj1.a();    // 「1 : 2」と表示される

var obj2 = new MyObj();
obj2.b.push( 3 );
obj2.a();    // 「1 : 2 : 3」と表示される

var obj3 = new MyObj();
obj3.b.push( 4 );
obj3.a();    // ????

18 行目で表示されるのは次のどちらだろうか。

  • 1 : 2 : 4
  • 1 : 2 : 3 : 4

答えは後者、1 : 2 : 3 : 4である。

MyObj.prototype.b に代入された[ 1, 2 ]は配列オブジェクトへのリファレンスを表している(6 行目)。new すると、配列オブジェクトがクローンされるのではなく、リファレンスがコピーされる(12 行目)だけなので、それに push する(13 行目)とオブジェクトのプロトタイプ自体をいじることになってしまう。結果、次に new したとき(16 行目)には、すでに改変された MyObj.prototype.b を“継承”することになるのだ。

これを避けるには、コンストラクタでいちいち初期化するようにすればよい。次のように書くと、new する度に新鮮な配列オブジェクトが代入されるので、上に書いたような汚染は起こらない。

var MyObj = function(){
    this.b = [ 1, 2 ];
};
MyObj.prototype = {
    a: function(){
        alert( this.b.join( " : " ) );
    }
};

var obj1 = new MyObj();
obj1.a();    // 「1 : 2」と表示される

var obj2 = new MyObj();
obj2.b.push( 3 );
obj2.a();    // 「1 : 2 : 3」と表示される

var obj3 = new MyObj();
obj3.b.push( 4 );
obj3.a();    // 「1 : 2 : 4」と表示される

このような問題が起こるのは、プロトタイプのメンバにオブジェクトを設定したときだけである。リテラルを定数として設定するだけなら問題ないし、その方が可読性は上がるだろう。

var MyObj = function(){
    this.b = [ 1, 2 ];
};

MyObj.prototype = {
    a: function(){
        alert( this.b.join( " : " ) );
    }
       ,name: "delphinus"
       ,species: "human"
};

コメントを残す