JSX で Function#apply を使う

JSX の built-in.jsx の Function の定義には

Unlike JavaScript, JSX does not provide Function#call() or Function#apply() since it is a statically-typed language.

と記述されており、Function#apply はサポートしない方針であることが伺えます。
と言っても、既存のコードで可変長引数の関数に対して長さ不明の引数を渡すために Function#apply が使われている場合は移植に困ります。

例えば次のようなコードです。

function foo(array1, array2) {
    Array.prototype.push.apply(array1, array2);
    return String.fromCharCode.appply(null, array1);
}

Array#push はどうも Array#concat を使った方が Android 標準ブラウザや Chrome で圧倒的に速いようなので1、concat 使えばいいんですが、String.fromCharCode を for ループで書き直すと処理に時間がかかってしまってしまいます2

実は JSX には apply を使うために用意されている関数(static メソッド)があるんです!
js.jsx を見てみると js.invoke という関数が定義されていることがわかります。

final class js {

	static var global : Map.<variant>;

	static native function invoke(obj : variant, funcName : string, args : Array.<variant>) : variant;

}

js.invoke を使って先ほどの foo 関数のようなメソッドを定義すると次のようになります。

import "js.jsx";

class _Main {
    static function foo(array1 : number[], array2 : number[]) : string {
        js.invoke(array1, "push", array2 as __noconvert__ variant[]);
        return js.invoke(String, "fromCharCode", array1 as __noconvert__ variant[]) as string;
    }

    static function main(args : string[]) : void {
        var str1 = "ABCDEFG";
        var str2 = "HIJKLMN";
        var chars1 = [] : number[];
        var chars2 = [] : number[];
        for (var i = 0; i < str1.length; i++) {
            chars1[i] = str1.charCodeAt(i);
            chars2[i] = str2.charCodeAt(i);
        }

        log _Main.foo(chars1, chars2);
    }
}

foo メソッドの変換結果を見ると次のように apply が使われていることがわかります。

_Main.foo$ANAN = function (array1, array2) {
	(function (o, p, a) { return o[p].apply(o, a); }(array1, "push", array2));
	return (function (o, p, a) { return o[p].apply(o, a); }(String, "fromCharCode", array1)) + "";
};

以上、あまり推奨されるやり方ではないでしょうが(そしてもっと良い方法が提供される可能性がありますが)、とりあえず Function#apply も使おうと思えば使えますよというお話でした!

  1. cf. http://jsperf.com/array-push-vs-array-concat-vs-for-loop 最初は配列の定義を setup のところに書いていて、push によってテストの度に要素数が増えてしまってるのか?と思いましたが毎回定義し直すようにしても結果はあまり変わりませんでした。 

  2. cf. http://jsperf.com/apply-vs-for-loop-as-for-string-fromcharcode