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" };