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

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

# コンストラクタ

```js://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);
};
```

## 初期化処理

```js:https://github.com/jashkenas/backbone/blob/b9665843568c922f6249be2fc3538b7d52ba514c/backbone.js#L1040
_.extend(this, _.pick(options, viewOptions));
```

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

```js:https://github.com/jashkenas/backbone/blob/b9665843568c922f6249be2fc3538b7d52ba514c/backbone.js#L1049
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
```
`viewOptions`は`model`関連とか、`el`や`id`といった要素関連、`events`が対象です。
これ以外の情報をインスタンス生成時に渡す場合は`View#inititalize()`のほうで調整する必要があります・

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

```js:https://github.com/jashkenas/backbone/blob/b9665843568c922f6249be2fc3538b7d52ba514c/backbone.js#L1041
  this._ensureElement();
  this.initialize.apply(this, arguments);
```

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


# View#_ensureElement()

```js:https://github.com/jashkenas/backbone/blob/b9665843568c922f6249be2fc3538b7d52ba514c/backbone.js#L1165
    _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`が設定されていなければ`atteributes`や`id`、`className`、`tagName`などからDom要素を生成したうえで`View#setElement()`に渡します。`el`にセレクタの文字列などが設定されていれば素直に`View#setElement()`に渡します。

```js:https://github.com/jashkenas/backbone/blob/b9665843568c922f6249be2fc3538b7d52ba514c/backbone.js#L1091
    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インスタンスのメソッドとのバインドを行います。

# つまり

```js: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
  }
});
```

```js
new MyView({el:'#hoge'});
```

```js
new MyView({el:$('#hoge')});
```

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

# よくある間違い

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

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

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

```js
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()`を自分で呼び出して再設定しましょう。

```js
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関係くらい)流れを把握しておくとハマリにくくなると思います。