JSX の遅延評価にご注意

ハマることがあるかもなぁと思ったのでメモがてら書き残しておきます。
遅延評価はどの言語でもハマる人がいると思うんですが、実は JSX にも遅延評価が存在します。

例えば次のスクリプトを実行するとどんな値が出力されるでしょう?

class _Main {
    static var a = 1;
    static var b = _Main.a;

    static function main(args : string[]) : void {
        _Main.a = 2;
        log _Main.b;  // ??
    }
}

答えはなんと 2 です!

解説

_Main クラスの変換結果は次のようになっています。

/**
 * class _Main extends Object
 * @constructor
 */
function _Main() {
}

_Main.prototype = new Object;
/**
 * @constructor
 */
function _Main$() {
};

_Main$.prototype = new _Main;

/**
 * @param {Array.<undefined|!string>} args
 */
_Main.main$AS = function (args) {
	_Main.a = 2;
	console.log(_Main.b);
};

var _Main$main$AS = _Main.main$AS;

_Main.a = 1;
$__jsx_lazy_init(_Main, "b", function () {
	return _Main.a;
});

最後の $__jsx_lazy_init の部分が曲者で、static 変数に対してリテラル以外で初期化すると $__jsx_lazy_init によって初期化されるようです。名前から想像がつくと思いますが、これによってこの変数が使われるときに初めて初期化が行われます。

$__jsx_lazy_init の定義は次のとおりです。

/**
 * defers the initialization of the property
 */
function $__jsx_lazy_init(obj, prop, func) {
	function reset(obj, prop, value) {
		delete obj[prop];
		obj[prop] = value;
		return value;
	}

	Object.defineProperty(obj, prop, {
		get: function () {
			return reset(obj, prop, func());
		},
		set: function (v) {
			reset(obj, prop, v);
		},
		enumerable: true,
		configurable: true
	});
}

Object.defineProperty の getter を使うことで JavaScript で遅延評価を実現しています。今回の例では、_Main の b プロパティにアクセスがあった時に初めて _Main.a が評価され、その値が _Main.b にセットされるようになっています。
よって、_Main.b にアクセスする前に _Main.a の値を変えると、_Main.b にアクセスした時には変更後の _Main.a を使って初期化が行われることになります。

わざわざ $__jsx_lazy_init を使っているのは、おそらく2つのクラスがお互いの static 変数を参照して初期化するような場合に対処するためだと思います。が、それだと static 変数を初期化するフェーズを main の実行前に設ければ良いだけの気もします。

何はともあれ、遅延評価には気を付けましょう!

広告
JSX で Function#apply を使う Introduction to Favmemo for Immature Engineers
※このエントリーははてなダイアリーから移行したものです。過去のコメントなどはそちらを参照してください