contextを使ってfat arrowなメソッドを書きなおそう


Backbone.jsとCoffeeScriptをお使いのみなさん、こんにちは。

イベントのcallbackに登録するメソッドをfat arrowで定義してませんか?
`Backbone.Events#on`の3番目の引数`context`を使いましょう。

メソッドをfat arrowで定義するとmixin出来ない問題についてもちょこっと触れます。


## 例
- イベント通知のために、グローバルな`PubSub`が定義されています。
- `NyaModel#initialize`で、`NyaModel#sayMessage`メソッドを`PubSub`に登録しています。
- `PubSub`から`(」・ω・)」うー！`イベントを受け取ると、`NyaModel#sayMessage`が呼ばれます。
- `NyaModel#sayMessage`は、`NyaModel`の`message`属性をコンソールに出力します。


## 今までの僕のfat arrowなやりかた

`PubSub`のcallbackに渡すメソッドの中で`NyaModel`の属性を使いたい。
だがしかし`PubSub`のcallback関数中では`this`が`PubSub`になる。
`this`を`NyaModel`に固定するため、メソッドをfat arrow(`=>`)で定義してた。


``` nyaru.coffee
window.PubSub = _.clone(Backbone.Events)

class NyaModel extends Backbone.Model
  defaults:
    message: '(／・ω・)／にゃー！'

  initialize: ->
    PubSub.on '(」・ω・)」うー！', @sayMessage

  # thisがNyaModelにbindされるようfat arrowで定義
  sayMessage: =>
    console.log @get('message')

PubSub.on '(」・ω・)」うー！', ->
  console.log '(」・ω・)」うー！'

new NyaModel
PubSub.trigger '(」・ω・)」うー！'
PubSub.trigger '(」・ω・)」うー！'

new NyaModel message: "Let's＼(・ω・)／にゃー！"
PubSub.trigger '(」・ω・)」うー！'

# (」・ω・)」うー！
# (／・ω・)／にゃー！
# (」・ω・)」うー！
# (／・ω・)／にゃー！
# (」・ω・)」うー！
# (／・ω・)／にゃー！
# Let's＼(・ω・)／にゃー！
```

## contextを使ったfat arrowじゃないやりかた
`Backbone.Events#on`の第3引数でcontextを与えると、callback関数中の`this`がcontextになります。
これでfat arrowを使う理由がなくなった。

``` nyaru.coffee
window.PubSub = _.clone(Backbone.Events)

class NyaModel extends Backbone.Model
  defaults:
    message: '(／・ω・)／にゃー！'

  initialize: ->
    # contextとして@を渡してあげる
    PubSub.on '(」・ω・)」うー！', @sayMessage, @

  # context渡すとthisがNyaModelになるのでfatじゃなくておｋ
  sayMessage: ->
    console.log @get('message')

PubSub.on '(」・ω・)」うー！', ->
  console.log '(」・ω・)」うー！'

new NyaModel
PubSub.trigger '(」・ω・)」うー！'
PubSub.trigger '(」・ω・)」うー！'

new NyaModel message: "Let's＼(・ω・)／にゃー！"
PubSub.trigger '(」・ω・)」うー！'

# (」・ω・)」うー！
# (／・ω・)／にゃー！
# (」・ω・)」うー！
# (／・ω・)／にゃー！
# (」・ω・)」うー！
# (／・ω・)／にゃー！
# Let's＼(・ω・)／にゃー！
```

## fat arrowのままでもいいじゃないの?

確かに今までの例だとその通りです。

しかしfat arrowのままだとmixinなどで`sayMessage`を入れ替えたいときに問題が生じます。

## fat arrowはmixinできない
fat arrowなmethodをmixinするとき、同じようにfat arrowを使ってこう書くのは間違いです

``` coffeescript
_.extend NyaModel::
  sayMessage: =>
    console.log "(」・ω・)」うー！ #{@get('message')}"
```

これは次のようなJavaScriptに展開されます。

```` javascript
var _this = this;

_.extend(NyaModel.prototype, {
  sayMessage: function() {
    return console.log("(」・ω・)」うー！ " + (_this.get('message')));
  }
});
```

`this`がトップレベル、ブラウザだと`window`オブジェクトにbindされてしまいます。

しかしfat arrowをやめると、`(」・ω・)」うー！`イベントで呼び出された際に、`this`が`PubSub`になってしまうのでエラーが出ます。

``` nyaru.coffee
window.PubSub = _.clone(Backbone.Events)

class NyaModel extends Backbone.Model
  defaults:
    message: '(／・ω・)／にゃー！'

  initialize: ->
    PubSub.on '(」・ω・)」うー！', @sayMessage

  sayMessage: ->
    console.log @get('message')

_.extend NyaModel::
  sayMessage: ->
    console.log "(」・ω・)」うー！ #{@get('message')}"

new NyaModel
PubSub.trigger '(」・ω・)」うー！'

# thisがPubSubにbindされちゃう＞＜
# => TypeError: Object #<Object> has no method 'get'
```

## contextがあればmixinも怖くない

``` nyaru.coffee
window.PubSub = _.clone(Backbone.Events)

class NyaModel extends Backbone.Model
  defaults:
    message: '(／・ω・)／にゃー！'

  initialize: ->
    PubSub.on '(」・ω・)」うー！', @sayMessage, @

  sayMessage: ->
    console.log @get('message')

_.extend NyaModel::
  # contextのおかげでthisがmodelになってる
  sayMessage: ->
    console.log "(」・ω・)」うー！ #{@get('message')}"

new NyaModel
PubSub.trigger '(」・ω・)」うー！'
PubSub.trigger '(」・ω・)」うー！'

new NyaModel message: "Let's＼(・ω・)／にゃー！"
PubSub.trigger '(」・ω・)」うー！'

# (」・ω・)」うー！ (／・ω・)／にゃー！
# (」・ω・)」うー！ (／・ω・)／にゃー！
# (」・ω・)」うー！ (／・ω・)／にゃー！
# (」・ω・)」うー！ Let's＼(・ω・)／にゃー！
```

## まとめ

- `Backbone.events#on`の3番目の引数`context`を渡すと、callback関数中の`this`が`context`にbindされる。
- イベントのcallbackに渡すメソッド中の`this`を固定するためにfat arrow(`=>`)を使う必要はありません。`context`を使いましょう。
- メソッドをfat arrowで定義すると、mixinできません。
  - 正確にはできるけど`this`をbindしなおす必要があって面倒です。