imthinker::net

ふらふらうぇぶろぐ

いつの間にか Alloy が進化していました

Titanium Mobile 用の公式 MVC フレームワークである Alloy 。発表当時は本当に使い物になるのか、継続的に開発は続くのか、 Appcelerator 的にはどのような立ち位置になるのかなどなど、不明な点が多かったのですが、何だかんだで便利に使えている製品です。

そんな Alloy ですが、どうしてもこれまで納得ができない仕様が1つありました。それは、 View である XML に onClick イベントハンドラを仕込む場合は Controller 側の関数は「文」でなければダメという点です。

今回、 Alloy 0.3.4 を改めて見直してみると、改善が見られました。

経緯

JavaScript でよく使われる関数の定義方法は大まかに2つあります。1つが関数宣言文。もう1つが関数リテラル (lambda 式) です。

function hoge() {
    alert("関数宣言文");
}

var huga = function () {
    alert("関数リテラル");
};

どちらもよく使う関数の定義方法なのですが、関数宣言文にはホイスティングという機構が働きます。

hoge();

function hoge() {
    alert("関数宣言文");
}

関数の呼び出し命令の後に書かれた関数宣言文は、実行時には関数が存在しているスコープの先頭にまで「巻き上げられて」実行ができるというものです。これは関数リテラルではダメです。

var huga;

huga();  // huga is undefined

huga = function () {
    alert("関数リテラル");
};

もしも関数リテラルを使って関数を定義した場合に、先に関数の呼び出し命令を書いたならばこのような形で評価されてしまいます。これらが災いして、 Alloy では関数リテラルで定義した関数は View 側に記述した onClick からは呼び出すことができませんでした。

// Alloy が出力するコントローラのコード例
function Controller() {
    // 1. Alloy が出力するユーティリティコード群
    // 2. Alloy の View (XML+TSS) を Titanium API を呼び出す形に整形されたコード
    // 3. View で設定された onClick イベントハンドラ
    // 4. ユーザが書いた Controller のコード
    // 5. Alloy が出力するユーティリティコード群 その2
}

Alloy はこのようなコードを出力します。イベントハンドラは関数の名前を引数にとるため、関数リテラルで関数を書いてしまうとイベント登録時には、まだ関数が入っているはずの変数は undefined ということになってしまうのです。

Alloy 0.3.4 による改善

これに対して、 Alloy 0.3.4 を試してみたところ、関数リテラルで関数を定義した場合でも、 onClick イベントが正常に動作するようになりました。 Alloy が出力するコードに何らかの変化があったようです。

// Alloy 0.3.4 が出力するコントローラのコード例
function Controller() {
    // 1. Alloy が出力するユーティリティコード群
    var __defers = {};
    // 2. Alloy の View (XML+TSS) を Titanium API を呼び出す形に整形されたコード
    // 3. View で設定された onClick イベントハンドラ
    foobar ? $.__views.__alloyId1.on("click", foobar) : __defers["$.__views.alloyId1!click!foobar"] != 0;
    // 4. ユーザが書いた Controller のコード
    var foobar = function () {
        alert("__defers オブジェクトによって延滞登録");
    };
    __defers["$.__views.alloyId1!click!foobar"] && $.__views.__alloyId1.on("click", foobar);
    // 5. Alloy が出力するユーティリティコード群 その2
}

ちょっと見てみると、このようなコードを出力していました。この例では foobar が falsy だった場合は __defers オブジェクトに対応する名前のプロパティで値は true (!0) を取り、ユーザが実際に書いたコードの下で改めてイベントの登録を行っています。

この変更によってユーザが Controller に関数宣言文や関数リテラルを意識することなく書けるようになりました。

何が嬉しいのか? それは CoffeeScript 。

この変更の利益を最も享受できるのは、 CoffeeScript を使って Titanium Mobile を楽しんでいるユーザです。

hoge = ->
  alert "CoffeeScript の関数は全てリテラル"

CoffeeScript で関数を定義すると、 JavaScript に変換された後は全て関数リテラルになります。この仕様によって CoffeeScript で Alloy のコードを書く場合は、

# インライン JavaScript
`function hoge() {
     alert("せっかく Coffee を使っているのにこれはかっこ悪い");
}`

このようにインライン JavaScript を使わないと View 側に書いた onClick イベントハンドラが動作しませんでした。しかし、 Alloy 0.3.4 の __defers オブジェクトを使った延滞登録によって CoffeeScript で書いたコードがそのまま動作するようになりました。

Alloy をビルドするときに CoffeeScript を JavaScript にトランスレートするフックスクリプトに関しては @k0sukey さんのブログをご覧ください。

CODESTRONG!