LoginSignup
42
43

More than 5 years have passed since last update.

Backbone.Viewの初期化処理の流れの解説

Posted at

Backbone.jsの開発をしていると、(経験がないうちは)しばしばinitialize()のところでelとか$elとかeventsとか何がどうなってるのかわからなくなるのですが、ソースを読むと非常にシンプルな流れなのでざっと見ておくと参考になるのではないかというエントリーです。

コンストラクタ

//github.com/jashkenas/backbone/blob/b9665843568c922f6249be2fc3538b7d52ba514c/backbone.js#L1037
var View = Backbone.View = function(options) {
  this.cid = _.uniqueId('view');
  options || (options = {});
  _.extend(this, _.pick(options, viewOptions));
  this._ensureElement();
  this.initialize.apply(this, arguments);
};

初期化処理

https
_.extend(this, _.pick(options, viewOptions));

まず、特定のプロパティ(viewOptionsで定義)について、optionsに初期値がふくまれていれば自動的にViewインスタンスのプロパティに設定してくれます。

https
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

viewOptionsmodel関連とか、elidといった要素関連、eventsが対象です。
これ以外の情報をインスタンス生成時に渡す場合はView#inititalize()のほうで調整する必要があります・

要素の設定とイベントのバインディング処理

https
  this._ensureElement();
  this.initialize.apply(this, arguments);

次にView#_ensureElement()el とか $elなど要素にまつわる処理をしています。
最後に、View#initialize()がコールされています。
なお、this.initialize.apply(this, arguments);としているので、インスタンス生成時にコンストラクタが受け取ったオブジェクトがそのまま引き渡されているのがわかります。

View#_ensureElement()

https
    _ensureElement: function() {
      if (!this.el) {
        var attrs = _.extend({}, _.result(this, 'attributes'));
        if (this.id) attrs.id = _.result(this, 'id');
        if (this.className) attrs['class'] = _.result(this, 'className');
        this.setElement(this._createElement(_.result(this, 'tagName')));
        this._setAttributes(attrs);
      } else {
        this.setElement(_.result(this, 'el'));
      }
    },

ここでは elが設定されていなければatteributesidclassNametagNameなどからDom要素を生成したうえでView#setElement()に渡します。elにセレクタの文字列などが設定されていれば素直にView#setElement()に渡します。

https
    setElement: function(element) {
      this.undelegateEvents();
      this._setElement(element);
      this.delegateEvents();
      return this;
    },

ここでDom要素($el)のセットアップとイベントバインディングの処理が行われます。
すでに$elが存在する場合を想定して、いったん View#undelegateEvents()が呼ばれています。
View#_setElement()はセレクタ文字列かBackboneに指定されたセレクタ関数(jQueryなど)を渡すことで $elにjQueryオブジェクトなどが設定されます。
ここで$elプロパティが設定されますので、View#delegateEvents()を呼び出しeventsに定義された値を使い、$el内の要素に対するイベントとViewインスタンスのメソッドとのバインドを行います。

つまり

myView.js
var MyView = Backbone.View.extend({
  events: {
    'click .open': 'open'
  },
  initialize: function(options) {
     // この時点で、new MyView({***}) で渡されるmodel, el, eventsなどが設定され、
     // this.$elの準備が行われ、this.$el.find('.open').on('click', this.open)のようにイベントバインディング済みの状態になります。
  },
  open : {
    // do something
  }
});
new MyView({el:'#hoge'});
new MyView({el:$('#hoge')});

このどちらでも同じように動作します。当然、eventsに記載された#hoge .openの要素がないとクリックしても何も行われません。

よくある間違い

コンストラクタに$elを渡しても何も設定されない

var view = new MyView({ '$el': $('#hoge')});// view.el にも view.$elにも値が反映されない

MyView#initialize()や MyView#render()で Domを変更したらイベントが反映されない

var MyView = Backbone.View.extend({
  events: {
    'click .open': 'open'
  },
  initialize: function(options) {
    this.$el = options.$el;
  },
  open : {
    // 呼ばれません
  }
});

自動で設定されるので、MyView#initializeの時点で$elを上書きする、あるいはDomを変更するとeventsの設定が無効になることがあります。

もしどうしてもこのような処理を行う場合は、前後に View#undelegateEvents()View#delegateEvents()を自分で呼び出して再設定しましょう。

var MyView = Backbone.View.extend({
  events: {
    'click .open': 'open'
  },
  initialize: function(options) {
    this.undelegateEvents();
    this.$el = options.$el;
    this.delegateEvents();
  },
  open : {
  }
});

また、View#undelegateEvents()を呼ばずに $elを上書きするとメモリリークしたり、多重にイベントハンドラを設定してしまう可能性もありますので注意が必要です。

まとめ

Backbone.jsをつかっていて思うように行かないときは、ちょっと手を止めてソースコードを眺めてみると仕組みが理解できると思うのでオススメです。
Marionette.jsもViewの基本的なところの処理はほぼBackbone.jsのままなので(追加で処理しているのはui関係くらい)流れを把握しておくとハマリにくくなると思います。

42
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
43