es2-postprocessorでOpera8未満、IE5.5未満でも動くJavaScriptを書く
「これまでに確認したレガシーな DHTML ブラウザの ES3 サポートと実装のバグ」の各表のタイトルを修正。調査ミスを修正。(2022/11/24)
「これまでに確認したレガシーな DHTML ブラウザの ES3 サポートと実装のバグ」の調査ミスを修正し、調査項目を追加しました。(2022/11/20)
IE5 の「ES3 で追加された構文」の誤りを修正しました。(2022/11/04)
Google Code Prettify(2006年~, IE6+, Gecko 1.8+) は Ajax が登場した時代のライブラリです。これを全ての DHTML ブラウザで動くようにしたいと思い立っていろいろやりました。
全ての DHTML ブラウザとは、IE5(1999年)以上、Gecko 0.6(2000年)以上(マイルストーンまでは頑張っていません…)、Opera7(2003年)以上、WebKit とします。独自な DOM 実装の IE4(1998年)については、ぼちぼち頑張ります。KHTML もおいおい…。
本件で得られた知見を水平展開すれば、Ajax 時代(2005年~)のコード資産の恩恵をよりレガシーなブラウザへもたらすことが出来るでしょう。多分。
1. これまでに確認したレガシーな DHTML ブラウザの ES3 サポートと実装のバグ
最初期の DHTML ブラウザの中には、IE6(2001年)をサポートするライブラリが動かないものがあります。これらのブラウザはデバッグ機能も貧弱で対応は困難を極めたりします。あらかじめこのような対応表を参照することで状況は一歩前進します。多分。
構文エラーが解消するバージョン
ブラウザのサポートバージョン | コード例 | IE | Opera | Gecko | WebKit | ||
---|---|---|---|---|---|---|---|
Mobile | PC | ||||||
ES3 で追加された構文 | instanceof , try~catch , throw
| ? | 5.0(*1) | ✔ | ✔ | ? | |
in オペレーター | "length" in []
| ? | 5.5(*1) | ✔ | ✔ | ? | |
ラベル付きステートメント | a: {break a}
| ? | ✔ | 7.5(*2) | ✔ | ? | |
Object リテラル | 数値のプロパティ | {1 : 1}
| ? | 5.5(*3) | ✔ | ✔ | ? |
RegExp リテラル(*4) | /./
| ? | ✔ | ✔ | ✔ | ? | |
global, ignoreCase | /./g, /./i
| ? | 5 | ✔ | ✔ | ? | |
multiline | /./m
| ? | 5.5 | ✔ | ✔ | ? |
- 対処法は 2. を参照。
「ラベル付きステートメント」を使うコードはほぼ見ませんが、Closure Compiler がアグレッシブにコードの最適化を行った結果、出力されることがあります。
do{break}while(false)
に書き換えることで構文エラーを回避できます。IE5 以下の Object リテラルのプロパティは数値文字列には対応するので、コードを書き換えます。Closure Compiler は常に Object リテラルの数値文字プロパティを数値に書き換えてしまうので、es2-postprocessor で修正します。
- JavaScript Object Reference > RegExp (Built-in Object) の各構文の実装状況。
実行時に起きるエラーや問題が解消するバージョン
コード例 | IE | Opera | Gecko | WebKit | |||
---|---|---|---|---|---|---|---|
Mobile | PC | ||||||
null への for in | for(key in null)
| ? | ✔ | 7.5 | ✔ | ? | |
Object リテラル | 空文字列のプロパティ | {"" : "empty"}
| ? | ✔ | 8(*1) | ✔ | ? |
window や DOM プロパティの削除 | delete window.__apply
| ? | 9(*2) | ✔ | ✔ | ? | |
空の配列の操作でエラーを投げる | --array.length
| ? | 5.5(*3) | 8(*4) | ✔ | ? |
- buggy な挙動は「Javascript 実装状況と深刻なバグ」で確認してください。
- 未検証ですが
ActiveXObject
や CSSOM でも起きそうです。ofk 氏によるFunction.prototype.apply
の polyfill からの変更点。 - IE5 では空の配列に
--this.length
の操作を行うと、長さが 4294967296 の配列になってしまう。shift と pop の polyfill で問題を起こしていました。 - Opera 7.x は空の配列に
--this.length
の操作を行うと、エラーは起きずthis.length === 0
です。
2. ES3 をよりレガシーな DHTML ブラウザ用に書き換えるポイント
2.1. instanceof
オペレーター
instanceof
の替わりに constructor
を調べる。継承クラスの可能性がある場合は、__proto__
を使えない為、スーパークラスを辿る仕組みを用意する必要があります。
次のコードは、ブラウザの開発メニューのコンソールで試す為のコードです。
(function(){
function SuperClass(){};
function SubClass(){};
SubClass.prototype = new SuperClass();
SubClass.prototype.__super__ = SubClass.prototype.constructor;
SubClass.prototype.constructor = SubClass;
return (function(instance, Class){
if(instance != null){
var proto = instance;
do{
if(proto.constructor === Class){
return (instance instanceof Class) ? 'Good.' : 'Bad!';
};
proto = proto.__super__ && proto.__super__.prototype;
}while(proto);
};
return (instance instanceof Class) ? 'Bad!' : 'Good.'
})(new SubClass(), SuperClass);
})();
但し、このような簡単なコードの場合、new SubClass(), Object
などの組み込みオブジェクトの調査に失敗します。
2.2. try~catch
ステートメントと throw
ステートメント
そもそも、エラーの出る条件を特定して、条件に合致した場合は、トライしない。
エラーの発生する可能性のあるブラウザオブジェクトに VBScript で触ってエラーをキャッチする(Windows 版 IE のみ, document.activeElement
など。)。
または非同期に書き換える事が許されるなら、タイマー(setTimeout
)のコールバック内でエラーの可能性のある関数を実行して、エラーが起きた場合は window.onerror
ハンドラーでコールバック等の処理をする。(未検証)
2.3. in
オペレーター
in
の替わりに this.XX !== undefined
か調べる。undefined
のプロパティが存在する場合は for in
文を使う。
function isMember(key, obj){
if(obj != null){
if(obj[key] !== void 0){
return true;
};
for(var prop in obj){
if(prop === key) return true;
};
};
return false;
};
また for(prop in null)
は Opera~7.2x でエラーになるので、事前に null
のチェックをする事。
3. es2-postprocessor のご紹介
ラベル付きブロックを何度か手動で書き換えた後に、AST ライブラリを使った Javascript の書き換えに初挑戦して制作したポストプロセッサです。まだテストは書いていません。テストも書き始めました。(2022/11/02 追記)
AST ライブラリを使った開発に着手するにあたって「JS の AST を扱うライブラリをつかって、不要な eval
呼び出しを除くコードを書いてみた」が良い入門記事でした。
本モジュールは ReRE.js for ES2 と ES2 Code Prettify のビルドタスクで使っています。