JScript の条件コンパイルが有効時(@cc_on)、//@...、/*@...@*/ 内が解析される。不要なら @cc_on を消せば、単なるコメントとして捨てられる。//@cc_on
//@set @debug_mode = true;
function hoge (a) {
/*@
if (@debug_mode) {
output(a);
}
@*/
return a;
}
>>1-2 にもあるが、@if...@elif...@else...@end で解析部を分岐できる。組み込み変数を使えば JScript のバージョンやプラットフォームも分かる。
//@cc_on
function hoge (a) {
/*@if (@_jscript_version < 5.0)
alert('IE 4.0');
@elif (@_jscript_version < 5.1)
alert('IE 5.0');
@elif (@_jscript_version < 5.5)
alert('IE 5.01');
@elif (@_jscript_version < 5.6)
alert('IE 5.5');
@else
alert('IE 6.0 or higher');
@end
@*/
}
例えば、try...catch 文がサポートされたのは JScript 5.0 からで、IE4 では構文エラーが出る。エラーを出さないようにするには、条件コンパイルで IE4 には if 文を使えば良い。過去の実装にとって未知の構文でも同一ソースに書くことができるわけだ。
いわゆるブラウザ分岐に関して言えば、未だに下記のようなのが散見される。
if (document.getElementById) {
...;
} else if (document.all) {
...;
} else if (document.layers) {
...;
}
これには問題がある。第一に、getElementById は DOM Level 1 では HTML モジュールだが Level 2 では Core モジュールに変更された。つまり、上記の書き方では DOM Level 2 HTML をサポートしているか判定できない。第二に、document.all はもはや IE だけのものではないこと。document.all 判定に通っても、IE 用のオブジェクトを実装している保証はない。第三に、上にも書いたように、IE でもバージョンによってサポートするものが違うということ。
ゆえに、分岐するのであれば、これから使用するプロパティの有無を判定しなければならない。
function addEvent (node, type, listener, useCapture) {
if ('undefined' != typeof node.addEventListener) {
return node.addEventListener(type, listener, useCapture);
}
if ('undefined' != typeof node.attachEvent) {
return node.attachEvent('on' + type, listener);
}
throw Error('NOT_SUPPORTED_ERR');
}
しかし、この関数を呼び出すたびにブラウザ分岐するのは無駄だ。下記のように最初に 1 度だけ関数を作成すれば、ほとんどのケースでは事足りる。
var contains = (function () {
if ('undefined' != typeof document.addEventListener) {
return function (node, type, listener, useCapture) {
return node.addEventListener(type, listener, useCapture);
};
}
if ('undefined' != typeof document.attachEvent) {
return function (node, type, handler) {
return n.attachEvent('on' + type, handler);
};
}
return function () {
throw Error('NOT_SUPPORTED_ERR');
};
} )();
もっとも、関数式にしてしまうと実行順序が面倒になる。関数宣言のまま処理冒頭に一度だけブラウザ分岐させたい場合、条件コンパイルが便利だ。
//@cc_on
function addEvent (node, type, listener, useCapture) {
try {
return node.
/*@if (5 <= @_jscript_version)
attachEvent('on' +
@else@*/
addEventListener(
/*@end@*/
type, listener, useCapture);
} catch (e) {
throw Error('NOT_SUPPORTED_ERR');
}
}
こうすれば、少なくとも IE 用の分岐はソースコンパイル時の一度だけで済む。実行中にいちいちブラウザ分岐させないようにすれば、ユーザスクリプトとしての再利用も容易になる(GreaseMonky とか user.js とか)。
まあ実際にはもう少しうまく書く必要があるのだが、数十行程度のコードではべた書きしてしまっています。また、これを参考にちょっとしたプリプロセッサを作れば、1 つのソースコードから様々なソースコードを自動生成できる。