フロントエンドのイベントキュー

6366 ワード

なぜjsは単一スレッドなのか
jsが単一スレッドを採用したのは、最初はブラウザを複雑にしたくないためです.マルチスレッドにはリソースを共有し、お互いの実行結果を変更する可能性があるため、Webスクリプト言語では複雑すぎます.例えば、jsには2つのスレッドが同時に存在し、1つのスレッドがあるDOMノードにコンテンツを追加し、もう1つのスレッドがこのノードを削除したと仮定すると、ブラウザはどのスレッドを基準にすべきですか?Javaではこの競合条件を解決するためにロックが使用されますが、jsはこのように解決したくありません.もちろん、単一スレッドモデルは、主に新しいタスクがキューの末尾に追加され、前のすべてのタスクが実行されてから実行の番になるという問題をもたらします.タスクが特に時間がかかると、後続のタスクが停止して待機し、ブラウザが応答を失い、「偽死」とも呼ばれます.「偽死」を回避するために、ある操作が一定時間後に終了できない場合、ブラウザはプロンプトボックスを飛び出し、ユーザーにスクリプトの実行を強制的に停止するかどうかを尋ねます.
イベントキュー
まず次のコードを見てみましょう
   function fn(){
       var a = 1;
       setTimeout(function(){
           var b = 2;
           console.log('b', b);
       }, 0)
       console.log('a', a);
   }
   fn();
   var c = 3;
   console.log('c', c);

通常の単一スレッドの理解によれば、出力は
   b 2
   a 1
   c 3

しかし、コードの実行結果は
   a 1
   c 3
   b 2

なぜsettimeoutのコードが最後に実行されたのか、jsの実行は単一スレッドであるためです.WindowsのsettimeoutやsetIntervalのような非同期タスクに遭遇すると、jsは黙ってこれらのコールバックを実行するのではなく、他のjsスクリプトを下に実行し続け、すべてのjsスクリプトが解析的に実行されるまでコールバックを実行します.では、複数のコールバックがある場合の実行順序はどうでしょうか.ブラウザはマルチスレッドであり、js実行スレッドは複数のスレッドのうちの1つにすぎません.jsの実行スレッドがsettimeoutを見ると、ブラウザはすぐに他のスレッドを呼び出してこの関数のコールバックをブラウザのイベントキューに投げ込み、イベントキューは先入先出のキューである.では、js実行スレッドがすべてのスクリプトを実行して空きがある場合、イベントキュー内のイベントコールバックは、1つずつ実行されます.ブラウザには内部の大きなメッセージループEvent Loop(イベントループ)があり、イベントキューをポーリングしてイベントを処理します.たとえば、ブラウザがonclickイベントの処理に追われている場合、別のイベント(input onchangeなど)が発生し、この非同期イベントはイベントキュー待機処理に格納され、前の処理が完了してから次の処理が実行されます.
    setTimeout(function(){
        setTimeout(function(){
            console.log(1);
        }, 1000);
        setTimeout(function(){
            console.log(1);
        }, 2000);
        setTimeout(function(){
            console.log(1);
        }, 3000);
        var time = new Date().getTime();
        while(true){
            //      6s   
            if(new Date().getTime() - time > 6000){
                break;
            }
        }
    }, 0)

ここでは6秒のタスクの後に連続した3つの1を見ることができます.setIntervalは異なり、現在のタスクをプリエンプトしてキューに配置するわけではありませんが、現在のスレッドが終了した後に間隔キューの追加が開始されます.
    setTimeout(function(){
        setInterval(function(){
            var div = document.createElement('div');
            div.innerHTML =  'I am a interval';
            document.body.appendChild(div);
        }, 1000);
        var time = new Date().getTime();
        while(true){
            //      3s   
            if(new Date().getTime() - time > 3000){
                var div = document.createElement('div');
                div.innerHTML =  'three seconds task ends';
                document.body.appendChild(div);
                break;
            }
        }
    }, 0);

ここではまず3秒計時を行い、さらに1秒ごとにDOMに‘I am a interval’を出力します.setIntervalが現在のスレッドをプリエンプトしていないことを示し、スレッドが終了した後にタイミングを開始します.(ここでは@伊優01の指摘に感謝します).
ajax
ではajaxは.ajaxの原理はまったく同じで、jsの実行スレッドがajax要求を発行すると、jsスクリプトを実行し続けます.応答が返されると、xhrオブジェクトに登録されたリスニングイベントに登録されたコールバック処理関数がタスクキューに格納されます.スクリプトがすべて実行されると、イベントキューに格納されたコールバック処理関数が実行されます.ajaxを送信するたびに、ブラウザは新しいスレッド処理を開き、これらのスレッド間でデータを共有します.したがって、ajaxリクエストを3つ同時に送信したい場合、コールバック関数を処理するときに同じ変数を操作したい場合、どのリクエストが先に返されるか分からない場合は、promiseオブジェクトを使用してコールバック関数の順序を決定することが望ましい.
jsダウンロード
jsはブラウザでダウンロード、解釈、実行の3つのステップを必要とします.html bodyラベルのscriptはブロックされています.つまり、ダウンロード、解釈、実行の順番です.例えばscript 1はscript 2の前にあるが、script 2が先にダウンロードされても始まらない.すべてダウンロードされてから解釈と実行が始まるからだ.Chromeは、script file、image、frameなどのマルチスレッド並列ダウンロード外部リソースを実現することができる(cssは複雑で、IEではダウンロードをブロックしないが、Firefoxはダウンロードをブロックする).ただし、jsは単一スレッドであるため、ブラウザはjsのダウンロードを同時に高速化することができるが、順次実行する必要がある.もう一つ注意しなければならないのは、ブラウザがに出会ってからコンテンツを表示し始めるので、jsスクリプトはラベルに置かないほうがいいです.jsのダウンロード、解釈、実行はhtmlの解析をブロックし、ページが最初から空白になる可能性があります.