Backbone.js
Marionette.js

Backbone(Marionette)でハマりそうな気がしなくもないパターン

More than 3 years have passed since last update.

とあるコードで

var View = Backbone.View.extend({

data: [],
initialize: function() {
this.data.push($('#hoge'));
},
reset: function() {
this.data = [];
}
});

みたいなのがあったんですが、これ若干はまりポイントがあるので注意です。


何故か

Backbone.extendによるコンストラクタ生成の時に指定したオブジェクトのプロパティは 全てprototypeオブジェクトのプロパティとして定義 されます。

つまり、上記のコードを通常のJavaScriptのコンストラクタ生成パターンで示すと

function View() {

}

View.prototype = {
data: [],
initialize: function() {

},
reset: function() {

}
}
// 本来ならBackbone.Viewのprototypeとかの処理も入りますが省略

のような定義と同様になるため、インスタンスを作成した時に

var view = new View();

console.log(view.__proto__.data); // -> []
console.log(view.__proto__.initialize); // -> function() {}
console.log(view.__proto__.reset); // -> function() {}

のように プロトタイプチェーンの参照オブジェクト(__proto__)上に定義 されます。まぁそりゃそうですね。


で、何を注意しなければならないか

注意すべきは、参照と代入です。

意図せずプロトタイプチェーン上の__proto__オブジェクトに存在するプロパティへアクセスを行ったり、逆にさっきまで触っていたデータへアクセス出来なくなるなどの可能性があります。

また、インスタンスに付く__proto__オブジェクトはprototypeオブジェクトの参照なので全インスタンス間とコンストラクタのprototypeで共有されるのも注意です。

var View = Backbone.View.extend({

data: [],
});
var view = new View();

// view.data に $('#hoge') を push しようとするが、view に data というプロパティは存在しないので
// プロトタイプチェーンを遡って this.__proto__.data へ代入される
view.data.push('hoge'); // view.__proto__.push('hoge') と同等

// view.data に [](空配列) を代入しようとすると、view オブジェクトの data というプロパティへ空配列が定義される
view.data = []; // この時点で view.data と view.__proto__.data が存在することになる

console.log(view.data); // -> [](空配列) (view オブジェクトに data プロパティは存在するので)

// では、view.dataを削除したとすると?
delete view.data;
console.log(view.data); // -> ['hoge'] (view.__proto__.data は存在するので)

// view.__proto__.data を消したくても delete view.data では消せないので
// 勢い余って delete view.__proto__.data などとすると…
delete view.__proto__.data;
console.log(view.data); // -> undefined
console.log(view.__proto__.data); // -> undefined
console.log(View.prototype.data); // -> undefined (view.__proto__ === View.prototype なので当然コンストラクタ側のものも消えてしまう)

みたいなのにハマったりしました。まぁそんなに話題に出ないのでエッジケースではあるのですが。


あとはこんなことが起こりそう


  • リセット or delete したはずなのに__proto__側が参照を持っていてメモリリークが発生し突然の死

  • 複数インスタンスで__proto__オブジェクトが共用されていて訳分からんデータで埋まったり消えたりして突然の死

  • 今どこのオブジェクトにデータを入れたのか分からなくなって突然の死

などなど。人類の可能性は無限大だぜ!

これからもprototypeのことをよろしくな!!



余談

個人的にはBackbone.Hoge.extendに渡すオブジェクトへ、ダイナミックな操作を行うオブジェクト(や配列)を入れなければいいんじゃねと思うます。そういうのはinitializeメソッドでやった方が確実に幸せになれます。

Backboneガチ勢はどうしてるんですかね。