スキップしてメイン コンテンツに移動

es2-postprocessorでOpera8未満、IE5.5未満でも動くJavaScriptを書く

「これまでに確認したレガシーな DHTML ブラウザの ES3 サポートと実装のバグ」の各表のタイトルを修正。調査ミスを修正。(2022/11/24)

「これまでに確認したレガシーな DHTML ブラウザの ES3 サポートと実装のバグ」の調査ミスを修正し、調査項目を追加しました。(2022/11/20)

IE5 の「ES3 で追加された構文」の誤りを修正しました。(2022/11/04)

Opera 7.54u2 ラベル付きの break で構文エラー」2022年2月2日 のツイート

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年)をサポートするライブラリが動かないものがあります。これらのブラウザはデバッグ機能も貧弱で対応は困難を極めたりします。あらかじめこのような対応表を参照することで状況は一歩前進します。多分。

構文エラーが解消するバージョン

ブラウザのサポートバージョンコード例IEOperaGeckoWebKit
MobilePC
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?
  1. 対処法は 2. を参照。
  2. ラベル付きステートメント」を使うコードはほぼ見ませんが、Closure Compiler がアグレッシブにコードの最適化を行った結果、出力されることがあります。

    do{break}while(false) に書き換えることで構文エラーを回避できます。

  3. IE5 以下の Object リテラルのプロパティは数値文字列には対応するので、コードを書き換えます。Closure Compiler は常に Object リテラルの数値文字プロパティを数値に書き換えてしまうので、es2-postprocessor で修正します。

  4. JavaScript Object Reference > RegExp (Built-in Object) の各構文の実装状況。

実行時に起きるエラーや問題が解消するバージョン

コード例IEOperaGeckoWebKit
MobilePC
null への for infor(key in null) ?7.5?
Object リテラル空文字列のプロパティ{"" : "empty"} ?8(*1)?
window や DOM プロパティの削除delete window.__apply ?9(*2)?
空の配列の操作でエラーを投げる--array.length ?5.5(*3)8(*4)?
  1. buggy な挙動は「Javascript 実装状況と深刻なバグ」で確認してください。
  2. 未検証ですが ActiveXObject や CSSOM でも起きそうです。ofk 氏による Function.prototype.apply の polyfill からの変更点
  3. IE5 では空の配列に --this.length の操作を行うと、長さが 4294967296 の配列になってしまう。shift と pop の polyfill で問題を起こしていました
  4. 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 のご紹介

npm > es2-postprocessor

ラベル付きブロックを何度か手動で書き換えた後に、AST ライブラリを使った Javascript の書き換えに初挑戦して制作したポストプロセッサです。まだテストは書いていません。テストも書き始めました。(2022/11/02 追記)

AST ライブラリを使った開発に着手するにあたって「JS の AST を扱うライブラリをつかって、不要な eval 呼び出しを除くコードを書いてみた」が良い入門記事でした。

本モジュールは ReRE.js for ES2ES2 Code Prettify のビルドタスクで使っています。