JavaScript の怪!?

  • 1 具現化するもの
  • 2 似て非なるもの
  • 3 郷に入っては郷に従うもの
  • 4 前提となるもの
  • 5 擬似スレッドなるもの

▲目次に戻る


1 具現化するもの

 プログラミング言語には オブジェクト指向 のものがある。ネットではオブジェクト指向論が様々に語られている。私は、この論に関して語る程の知見を持っていない。しかし、表現を具現化するための手法の一つである事は間違いない。 私が扱ってきたオブジェクト指向の言語は全てクラスベースと呼ばれるものだった。ブラウザ上でゲームを実装する為に JavaScript を使用する事にしたのだが、これはプロトタイプベースと言う 方式を採用している。Goole で Javascript を検索すると(2016/12/29時点) 約 2,220,000,000 件 がヒットした。サンプルコードを眺めて思った事は、難解な言語だなと。記述の自由度が高いと言えば 良いのか、迷宮に落ちていくような感覚である。
 さて困った。どんな言語でも様々な記述で同等の結果を得る事はできる。しかし生産性や保守性を考慮した場合には、記述ルールを適用するのがシステム屋の性だ。記述ルールを適用せずに書くと、 後になって書いた本人さえプログラムを追う事が困難になる。ここは私の知見に近い記述方法を採用したい。つまりクラスベースの様な記述が出来ないか、と思って探し回ったら、関数式と new 演算子 を使う事で可能である事を知った。下記のような記述が出来る。
// 関数式
var hoge = function() {
};
// 生成
var mage = new hoge();
関数式をクラス定義とみなすと
// クラス定義
var クラス名 = function() {
};
// 生成
var オブジェクト = new クラス名();
と考える事が出来る。クラス定義(関数式)の中で this を使用してプロパティを割り当てる。ゲームメインクラスは下記のような感じだ。
// ゲームメインクラス
var clsGameMain = function()
{
    // タブのID固定部
    this.idPreCmd = "cmd0";
    // タブ文言配列
    this.Tabs = {};
    // ゲームのタブオブジェクト
    this.Play = {};
    // ゲームのタブ画面オブジェクト設定
    this.Play.Cmd01 = new clsDevelope();
    this.Play.Cmd02 = new clsManagement();
    this.Play.Cmd03 = new clsItems();
    this.Play.Cmd04 = new clsNews();
    this.Play.Cmd05 = new clsChat();
    this.Play.Cmd06 = new clsLog();
    // 初期タブは開発タブ
    this.Play.Current = this.Play.Cmd01;
    // 座標計算オブジェクト生成
    this.gcMath = new clsMath();
    // 外交ボタン
    this.idDiplomacy = '#Diplomacy';
    // 外交の交渉状態の有無の視覚的要素のID
    this.idDiplomacyWait = 'DiplomacyWait';
    // 外交の交渉状態の有無の視覚的要素
    this.DiplomacyWaitElement = null;
};
メソッドは下記のように記述する。
// ゲームメイン実行
clsGameMain.prototype.Run = function()
{
    // ここに処理を記述する
}
これで私の土俵に落とし込む事ができる。

▲見出しに戻る

▲目次に戻る


2 似て非なるもの

 ネットで JavaScript はシングルスレッドであるとの説明を良く見かける。 スレッド とは、CPU利用の単位である。私には JavaScript で言うスレッドがコンパイラ言語のスレッドと同義とは思えない。しかし適切な言葉が見つからないので、あえて言うなら擬似スレッドと 呼ぶ事にする。コンパイラ言語では明示的にスレッドを生成する関数を提供するのが一般的だ。少なくともC言語ではそうである。OS が実行モジュールをロードし プロセス を生成する段階でコード領域(マシン語)、データ領域、スタック領域などのメモリを確保し実行が行われる。プロセスにはメインスレッドがあり、これは暗黙的に生成される。よって、プロセスは 一つ以上のスレッドを持つ。OS は CPU と連携しながらスレッドを順番に実行する。人間の目には並列に動いているように見える。
 コンパイラ言語の中には、仮想CPUの中間語を生成し、それを実行するフレームワークを提供するものがある。 Javaプラットフォームや .NET Frameworkなどが相当する(JavaとJavaScriptはまったく別物である)。これらは実行時に実行環境のCPUのマシン語に 翻訳される。一方で中間言語を介さずに実行時解釈を行う言語がある。PHPなどが相当する。PHP は明示的に スレッドを生成できるらしい。
 利用者からすると、スレッドが厳密にCPU利用の単位である必要はない。順番に実行される事が大切なのである。無限ループの処理 while(true); と記述されても、それを実行する環境がフリーズ する事がなく順次スレッドを実行する、あるいはCPUタイムアウトなどでスレッドを停止するなどの処置がとられているなら、真のスレッドと呼ぶ事にする。下記のHTMLで実験してみよう。
<!DOCTYPE html>
<html>
  <head>
    <!-- 表題 -->
    <title>無限ループ実験</title>
    <!-- 無限ループ処理 -->
    <script type="text/javascript">
      while(true);
    </script>
  </head>
  <body>
  </body>
</html>
実験環境と実験結果
OS:CentOS 6.8
ブラウザ:
 1) Google Chrome 55.0.2883.87
 2) Opera 12.16.1860
  1) と 2) は問題なく実行される。新しいタブを開く事ができて他のページを見る事が出来る。実験ページはループアイコンがタブバーに表示される。
 3) FireFox ESR 45.6.0
  メッセージが表示され選択肢が提供される。
「次回からは確認しない」をチェックし「処理を続行」ボタンを押す。新しいタブを開く事ができない。ブラウザが応答なしとなり何をしても無反応となる。ブラウザを強制終了するしかない。 「FireFoxってダメじゃん」と思っても仕方がない。ゲームのプレイヤーは、どのブラウザを使用するかわからない。
 実験でわかった事は、JavaScript のスレッドの挙動はブラウザに依存すると言う事である。HTMLがブラウザによって表現が違う事は良く知られている。HTMLも末尾に Language と付くので言語で あろう。言語である HTML がブラウザに依存するなら、同様に言語である JavaScript もブラウザ依存で問題無いだろう。などと寛容の心持ちにはなれない。なれないからと言っても仕方がない。 FireFox で動く記述をしたら、他のブラウザでも動くはずだと自身を慰めるしかない。

▲見出しに戻る

▲目次に戻る


3 郷に入っては郷に従うもの

 1993年に発売された「Windows 3.1」と言うOSがあった。このOS上でGUIアプリケーションを動かすには 1/10s ルールと言うものがあった。例えば、マウスのクリックを受け取り処理する場合は、 1/10s 以内に処理をして戻れよと。GUIアプリはイベント待ちループを持ち、OS からメッセージを受けとる。while(GetMessage(&msg, NULL, 0, 0)) で永久ループを記述する。このルールを無視 して下記のようなコードを実行するとどうなるか?
// メッセージループ
while(GetMessage(&msg, NULL, 0, 0)) 
{
  // 1/10s 以内に処理をして GetMessage ループに戻らない意地悪な実験
  while(1);
}
意地悪な実験としてあるが、この実験を行う環境が私の手元にはない。恐らく「Windows 3.1」が動く環境は、どこぞの博物館にしかないだろう。だから私の手元に「Windows 3.1」があった頃の実験の 話である。結論を言えば OS 自体がフリーズし電源を切って再起動となる。現在(2017年1月時点)の windows OS では実験用のアプリのみがフリーズし OS 自体はきっと元気であるだろう。
 さて、前節で JavaScript を「FireFox で動く記述をしたら、他のブラウザでも動くはずだ」と書いた。これは「郷に入っては郷に従う」記述をすると言う事だ。つまり何かしらのルール違反を 犯さない記述をするなら、ブラウザ自体はフリーズさせずに済む定石のような記述方法があるのではないか?これを探っていこうと思う。

▲見出しに戻る

▲目次に戻る


4 前提となるもの

 往々にして、Web アプリを作成する方々は、作成した Web アプリが全てのブラウザを対象に動く事を願うはずだ。企業が作成する Web アプリなら相等の資金が投入され、テスト環境も揃えられる だろう。ブラウザの省略時の表現でなく、明確にスタイルを設定し、フォームで画面を提供するなら可能かも知れない。また、PC用、モバイル用と個別の画面を作成する事も、資金的に可能であろう。 しかし私は個人であり、金はないしモバイルも持ち合わせていない。テスト環境はPC用のブラウザしかないし、全てのブラウザも用意出来ない。自ずと動作環境は絞られる。
 既に「HTML5 と canvas と CSS3 を使用する」事は前提としてある。よって、これらを解釈できないブラウザ(バージョンも含む)は動作環境から排除する。排除すると言っても積極的な排除ではない。 動くなら良いし動かないなら動かないままと言う、消極的な排除である。つまりブラウザの種別やバージョンを if で問うことをしない。とは言え自身で if 文を記述しないと言う事であり、無料で 提供される JavaScript ライブラリがクロスブラウザの部分を吸収してくれるなら、有難く恩恵を受けたい。そこで登場するのが jQuery である。これを使えば「クロスブラウザ対応を意識する必要がない」と言う。私には、真偽を確かめる術がない。だが、ネット上では、そのような記述が多くあるので、ここは素直に信じて救われる者に なるとしよう。

▲見出しに戻る

▲目次に戻る


5 擬似スレッドなるもの

 ブラウザ上で実行する JavaScript のスレッドの実装がどのようになっているかを、私は知らない。尤もオープンソース系のブラウザなら、ソースを見る事が出きるし解析も可能であろうが、私には 労力をかける気力がない。では無限ループの実装はどうするか? JavaScript では setInterval や setTimeout を使用してタイマーによるループ処理が一般的らしい。
 左図のような画像において、遠くで閃光がおこるようなシーンを表現してみよう。最初は1枚目の画像を表示する。10秒おきに2枚目の画像を表示する。2枚目の画像の表示時間は 0.2 秒間と する。あらかじめ2つの画像は同じ位置とサイズで HTML で記述されているものとする。1枚目の画像は可視であり2枚目の画像は不可視である。JavaScript のソースを下部に記載する。 これなら、FireFox も満足してくれる。無限ループの実装の仕方もわかった。
// 閃光を表現する
clsGameMain.prototype.FlashImage = function()
{
    // 間隔を初期化
    var Interval = 0;
    // 閃光無限ループ
    setInterval(
        // 0.2 秒毎に処理する無名関数を予約する。ブラウザは 0.2 秒毎に当関数を実行する
        function() {
            // 間隔を加算
            Interval += 200;
            // 10秒に達していたら閃光画像を可視化、以外は不可視にする。
            if (Interval >= 10000) {
                // jQuery を使用して閃光画像を可視化
                $('#flash').show();
                // 間隔をリセット
                Interval = 0;
            }
            else {
                // jQuery を使用して閃光画像を不可視
                $('#flash').hide();
            }
        }
        , 200
    );
}

▲見出しに戻る

▲目次に戻る