CoffeeScriptの@ではまった話

つまりJavaScriptのthisではまった話なんだけども。
まったくJavaScriptのthisってやつは・・・

駄目なコード

とあるAPIからhttpでデータを取得するという処理。
TitaniumとCoffeeScript。

class SomeAPI
    @items: []

    @searchItem: ->
        xhr = Ti.Network.createHTTPClient()

        url = "http://api.example.com/?query=hogehoge"
        xhr.open('GET', url)
        
        xhr.onload = ->
            doc = this.responseXML.documentElement
            @items = doc.getElementsByTagName('Item')
        
        xhr.onerror = (err) ->
            alert("ネットワークからデータを取得できませんでした。")
        
        xhr.send()

やりたいことはSomeAPI.itemsにAPIから取得したXMLをパースした内容を入れたいと言うこと。
しかしこのコードだとSomeAPI.itemsはいつまで経っても空の配列のままなんです。

@itemsになぜ値が入らない?

うまくいくコード

class SomeAPI
    @items: []

    @searchItem: ->
        that = this #// 注目
        xhr = Ti.Network.createHTTPClient()

        url = "http://api.example.com/?query=hogehoge"
        xhr.open('GET', url)
        
        xhr.onload = ->
            doc = this.responseXML.documentElement
            that.items = doc.getElementsByTagName('Item') #// ここも注目
        
        xhr.onerror = (err) ->
            alert("ネットワークからデータを取得できませんでした。")
        
        xhr.send()

that = thisという一見わけの分からない行が肝。
thatの代わりにselfを利用する人もいるようだ。

理由を説明するとxhr.onloadに入ると実行コンテキストが変わってthisの示す対象がxhrになってしまう。
当然@の示す対象もxhrになる。

一方that = thisを行っている時点ではthisがまだSameAPIを指しているので、ここでthatにはSameAPIが入る。
というわけでxhr.onloadの中でthatを使えば期待通りの処理ができるということだ。

つまり同じ@itemsでもコンテキストによって指しているのがSomeAPI.itemsとxhr.itemsだったりするということ。
最初のコードだとxhr.itemsに何かが入っていたことでしょう。

そもそもxhr.onloadの中でthisと@が並んでいるのを見て何の疑問を持たなかったのか俺よ。

教訓

thisを使うときはコンテキストに注意。特にクロージャ内のthis。

それとCoffeeScriptの@がthisを指していることを忘れてはならない。

つい先日JavaScript Good Partsで読んでいたのにそれと気がつくまでだいぶ時間を食ってしまった。
しかしこれで肝に銘じたぞ。

壁は一つではなかった

まだこれでもSameAPI.itemsを期待通りに使うには非同期処理の問題があったりしたのだけれど、それはまた別のお話。

前の記事

XCodeとRubyの思わぬ関係