Backbone.js 1.0に向けての変更点

UITチーム 清水大輔 (@tori3_jp)です。
2012年も残り僅かとなりましたが、年の瀬をいかがお過ごしでしょうか。

Backbone.js Advent Calendar 2012の10日目のエントリーとなります。

先月、DailyJSというJavaScript関連のトピックを配信しているサイトに
Backbone.jsの次期リリースに関する記事が投稿されていました。
今回はこの中からBackbone.jsの次期リリースで予定されている変更点について紹介したいと思います。

Backbone.$

Backboneで扱うjQuery互換ライブラリへのエイリアスが変更されます。
ローカル変数$を参照していたものがBackbone.$に変更されます。

- var $ = root.jQuery || root.Zepto || root.ender;
+ Backbone.$ = root.jQuery || root.Zepto || root.ender;

また、同時にsetDomLibrary()は削除されます。
使っているケースはほとんど無いと思いますが、どうしてもsetDomLibrary()の代わりになる処理が必要という場合は、下のように修正する必要がありそうです。

Backbone.$ = jQuery;

Backbone.Viewの対するmemory leakを解決するためにdispose()の追加

現在のバージョンではBackbone.View.remove()を使ってDOM上から削除した場合
collection / modelからの参照などは残ったままとなり
ガーベージコレクタで回収されずmemory leakを引き起こす可能性があります。

そのため、現在はDOMから削除する前に明示的にcollection / modelに対してoff(unbind)などでイベントリスナーの削除を行う必要がありますが、新たに追加されるdispose()がこの処理を行なってくれます。

また、remove()内部からdispose()が呼び出されるので、基本的なViewのパターンでは暗黙的に実行されるようになります。

    dispose: function() {
      this.undelegateEvents();
      if (this.model && this.model.off) this.model.off(null, null, this);
      if (this.collection && this.collection.off) this.collection.off(null, null, this);
      return this;
    }

Collection.add()のmergeをサポート

Collection.add()に重複するidを持つmodelがあった場合、該当のmodelは更新されませんでした。

追加されるoptions.mergeを”true”にすることで同じidを持つmodelがmergeされます。Collection内のデータを部分的に更新したい場合など楽に行えるようになります。

下のサンプルではoptions.mergeをtrueにした場合、id=1のmodelがmergeされ、nameが”dog”から”bear”に変化しています。

var items = new Backbone.Collection;
items.add([{id: 1, name: "dog", age: 3},{id: 2, name: "cat", age: 2} ]);

items.add([{id: 1, name: "bear"}], {merge: true}); // merge : true
items.add([{id: 2, name: "lion"}]); // merge: false

console.log(JSON.stringify(items.toJSON()));
// result : [{"id":1,"name":"bear","age":3},{"id":2,"name":"cat","age":2}]

その他、細かい変更点は下のようなものがあります。

  1. Collectionにclone()が追加され、複製を生成できる
  2. Collectionのcomparatorでfalseが設定可能 (インスタンス毎にoverrideしてsortを無効化できるようになる)
  3. 引数無しのoff()の場合にもメソッドチェインを利用可能になる
  4. save, create, destory, fetchなどのsuccess callbackの引数にoptionsが追加

まとめ

1.0へ向けては新機能やアーキテクチャの変更などの大きな変更は予定されておらず、バグフィックスやパフォーマンス改善、使い勝手の向上というところに主眼が置かれているようです。
AngularJSなどと比べると機能面では圧倒的に劣りますが、安定性や使いやすさという点でより洗練されていくことに期待したいところです。

追記 update 2012/12/17

このエントリー翌日 (2012/12/11) に変更があり、dispose()は廃止されました(-_-:wink:

代わってBackbone.Event.listenTo()とBackbone.Event.stopListening()という2つのメソッドが追加されました。
stopListener()はBackbone.View.remove()からcallされ、デフォルトでlistenTo()などで設定された参照は削除されますが、必要であればoverrideしてクリーンナップ処理を書く必要があります。

この件については現在もこちらでディスカッションされおり、listenTo()、stopListening()のメソッド名について意見が交わされているため、今後変更される可能性もあります。
また、これらの変更は先週2012/12/13にリリースされたv0.9.9に反映されています。

以下のような理由からdispose()は廃止となったようです。

  1. Viewのthis.model, this.collectionのみの対象となっているため
    複数のmodel, collectionを参照している場合などは参照が残ったままとなり
    memory leakの根本的な解決になっていない
  2. View.remove()で必ずしもdispose()を必要としなケースもあるため、無駄なコストが発生する。
    例えば、sort()などのadd(),remove()が頻繁に発生する場合、都度dispose()させる必要がない場合もありえる

同じような役割をするメソッドとしてon(), off()がありますが
on(), off()がmodel/collectionからviewにたいして呼び出すのに対し
listenTo(), stopListening()はviewからmodel/collectionへの使用されることが想定されているようです。(もしかしたら、今後、listenTo(), stopListening()に統一されるかも? )

on(), off()の場合

var dog = new Backbone.Model({name: 'dog'});
var view = new Backbone.View({
  model: dog,
  initialize: function() {
    this.model.on("all", this.render, this);
  },
  render: function() {
    ....
  }
});

listenTo(), stopListening()の場合

var dog = new Backbone.Model({name: 'dog'});
var foods = new Backbone.Collection([{name: 'fish'}, {name: 'beef'}]);
var view = new Backbone.View({
  model: dog

initialize: function() {
this.model.on("all", this.render, this);
},
render: function() {
....
},
eat: function() {
},
stopListening: function(other, event, callback) {
//override View.stopListening([other], [event], [callback])
}
});
view.listenTo(foods, 'add', view.eat);
....
view.stopListening(foods); // or view.remove();