ホームページ  >  記事  >  ウェブフロントエンド  >  setTimeout のイベント ループ モデルについて話しましょう

setTimeout のイベント ループ モデルについて話しましょう

巴扎黑
巴扎黑オリジナル
2017-04-29 14:43:051462ブラウズ

他のプログラミング言語(C#/Java)からJavaScriptに乗り換えた開発者としては、JavaScriptを学習する過程でsetTimeout()メソッドの動作原理が理解しにくい部分に遭遇しました。この記事では、他のプログラミング言語と組み合わせて、setTimeout イベント ループ モデルからの実装を試みます

1. setTimeout から始めましょう

setTimeout() メソッドは ecmascript 仕様では定義されていませんが、BOM によって提供される関数です。 w3school の setTimeout() メソッドの定義を参照してください。 setTimeout() メソッドは、指定されたミリ秒数の後に関数を呼び出すか、式を計算するために使用されます。

構文 setTimeout(fn, millisec)。fn は実行されるコードを表し、JavaScript コードまたは関数を含む文字列を指定できます。 2 番目のパラメータ millisec はミリ秒で表される時間で、fn をどれだけ遅延させる必要があるかを示します。

setTimeout() メソッドを呼び出した後、このメソッドは数値を返します。この数値は、タイムアウト呼び出しをキャンセルするために使用できる、計画された実行コードの一意の識別子です。

最初、setTimeout() の使用は比較的単純で、次のコードを見るまでその動作メカニズムを深く理解していませんでした

var start = new Date;
setTimeout(function(){
var end = new Date;
console.log('Time elapsed:', end - start, 'ms');
}, 500);
while (new Date - start < 1000) {};

setTimeout() の最初の理解では、遅延は 500 ミリ秒に設定されていたため、出力は経過時間: 500 ミリ秒になるはずです。なぜなら、直感的に理解すると、上記のコードを実行するとき、JavaScript 実行エンジンは上から下への順次実行プロセスになるはずであり、setTimeout 関数は while ステートメントの前に実行されるからです。しかし実際には、上記のコードを複数回実行すると、出力は少なくとも 1000 ミリ秒遅れます。

2.Java の setTimeout の実装

Java を学習した際の私の過去の経験を思い出して、前述の Javascript の setTimeout() に混乱しました。 Java には setTimeout 用の API 実装が複数あります。ここでは例として java.util.Timer パッケージを取り上げます。タイマーを使用して上記のロジックを Java に実装します。複数回実行すると、出力は経過時間: 501 ミリ秒になります。

りー

setTimeout() でこの違いが発生する理由を詳しく説明する前に、まず java.util.Timer の実装原理について説明します。

上記のコードの主要な要素は、Timer、TimerTask クラス、および Timer クラスのスケジュール メソッドです。関連するソース コードを読むと、その実装を理解できます。

Timer: Task タスクのスケジュール クラス。TimerTask タスクと同様に、ユーザーがスケジュール メソッドを通じてタスクの実行計画を調整するための API クラスです。このクラスは、TaskQueue タスク キューと TimerThread クラスを通じてタスクのスケジュール設定を完了します。

TimerTask: Runnable インターフェイスを実装し、各タスクが独立したスレッドであることを示し、ユーザーが run() メソッドを通じて独自のタスクをカスタマイズできるようにします。

TimerThread: Thread から継承され、実際にタスクを実行するクラスです。

TaskQueue: Task タスクを格納するデータ構造。ヒープの各メンバーは TimeTask であり、最小の nextExecutionTime を持つタスクが先頭になります。キューの最も早い実装が行われるようにします。

3. 結果に基づいて理由を見つけます

Java.util.Timer の setTimeout() 実装を確認した後、JavaScript の setTimeout() メソッドに戻って、前の出力が期待と一致しない理由を見てみましょう。

りー

コードを読むと、while() ループの前に setTimeout() メソッドが実行されていることがわかります。これは、このステートメント、つまり登録を 1 回実行することを「望んでいる」ことを宣言しています。無名関数の setTimeout() メソッド内にあり、実行後すぐに有効になります。コードの最後の行の while ループは 1000 ミリ秒間実行され続けます。 setTimeout() メソッドを通じて登録された匿名関数の出力の遅延時間は常に 1000 ミリ秒より大きく、この匿名関数の実際の呼び出しが 1000 ミリ秒よりも長いことを示しています。実際の呼び出しは while() ループによってブロックされます。 while() ループはブロック後に実際に実行されます。

Java.util.Timer では、スケジュールされたタスクのソリューションはマルチスレッドによって実装され、タスク オブジェクトはタスク キューに格納され、専用のスケジューリング スレッドが新しいサブスレッドでタスクの実行を完了します。 schedule() メソッドを通じて非同期タスクを登録すると、スケジューリング スレッドは子スレッドで直ちに動作を開始し、メイン スレッドはタスクの実行をブロックしません。

これがJavaScriptとJava/C#などの言語の大きな違い、つまりJavaScriptのシングルスレッド機構です。既存のブラウザ環境では、JavaScript 実行エンジンはシングルスレッドであり、メイン スレッドのステートメントとメソッドは、メイン スレッドのステートメントを実行した後にのみ、スケジュールされたタスクの実行をブロックします。タスク登録時に設定した遅延時間よりも長い時間がかかる場合があります。この時点で、JavaScript と Java/C# のメカニズムは大きく異なります。

4. イベントループモデル

シングルスレッド Javascript エンジンでは setTimeout() はどのように動作するのでしょうか? ここでは、ブラウザ カーネルのイベント ループ モデルについて説明します。簡単に言うと、JavaScript 実行エンジンの外側にタスク キューがあり、コード内で setTimeout() メソッドが呼び出されるとき、登録された遅延メソッドがブラウザ カーネル内の他のモジュールに渡されます (WebKit を受け取ります)。たとえば、Webcore モジュールです) 処理では、遅延メソッドがトリガー条件に達したとき、つまり設定された遅延時間に達したときに、この遅延メソッドがタスク キューに追加されます。このプロセスはブラウザ カーネルの他のモジュールによって処理され、実行エンジンのメイン スレッドから独立しています。メイン スレッド メソッドの実行が完了し、実行エンジンがアイドル状態に達した後、タスクからタスクを順番に取得します。このプロセスは、イベント ループ モデルと呼ばれます。

スピーチ内の情報を参照すると、上記のイベント ループ モデルは次の図で説明できます。

Javascript 実行エンジンのメインスレッドが実行されると、ヒープとスタックが生成されます。プログラム内のコードは 1 つずつスタックに入り、setTimeout() メソッドが呼び出されると、つまり図の右側の WebAPI メソッドが呼び出されると、ブラウザ カーネルの対応するモジュールが起動されます。遅延メソッドの処理 遅延メソッドがトリガー条件に達すると、メソッドはコールバック用のタスク キューに追加され、実行エンジン スタック内のコードが実行されている限り、メインスレッドはタスク キューを読み取り、それらのコールバックを実行します。トリガー条件を満たす関数を順番に実行します。

スピーチ内の例を使用してさらに詳しく説明します

setTimeout のイベント ループ モデルについて話しましょう

setTimeout のイベント ループ モデルについて話しましょう

図のコードを例にとると、実行エンジンが上記のコードの実行を開始すると、実行スタックに main() メソッドを追加するのと同じになります。 console.log('Hi') の開始を続けると、log('Hi') メソッドがスタックにプッシュされます。 console.log メソッドは、WebKit カーネルによってサポートされる一般的なメソッドであり、WebAPI に関与するメソッドではありません。前の図のように、log here ('Hi') メソッドはスタックから直ちにポップされ、エンジンによって実行されます。

setTimeout のイベント ループ モデルについて話しましょう

setTimeout のイベント ループ モデルについて話しましょう

console.log('Hi') ステートメントが実行されると、log() メソッドがスタックからポップアウトされ、Hi が出力されます。エンジンはダウンを続け、setTimeout(callback,5000) を実行スタックに追加します。 setTimeout() メソッドは、イベント ループ モデルの WebAPI のメソッドに属します。エンジンが実行のためにスタックから setTimeout() メソッドをポップすると、遅延実行関数が対応するモジュール、つまりタイマー モジュールに渡されます。図の右側にある処理用。

setTimeout のイベント ループ モデルについて話しましょう

実行エンジンが実行のためにスタックから setTimeout をポップすると、遅延処理メソッドを Webkit タイマー モジュールに渡し、すぐに次のコードの処理を続行するため、log('SJS') が実行スタックに追加されます。次に、 log('SJS' ) がスタックから実行され、SJS が出力されます。実行エンジンが console.log('SJS') を実行した後、プログラムが処理され、main() メソッドもスタックからポップされます。

setTimeout のイベント ループ モデルについて話しましょう

setTimeout のイベント ループ モデルについて話しましょう

setTimeout のイベント ループ モデルについて話しましょう

このとき、setTimeoutメソッド実行から5秒後に遅延処理メソッドがトリガ条件に達したことをタイマモジュールが検出し、タスクキューに遅延処理メソッドを追加します。このとき、実行エンジンの実行スタックは空になっているため、タスクキューに実行する必要のあるタスクがあるかどうかのポーリングを開始し、遅延メソッドが実行条件に達したことを検出して追加します。実行スタックへの遅延メソッド。エンジンは、遅延メソッドが log() メソッドを呼び出していることを検出したため、log() メソッドをスタックにプッシュしました。すると実行スタックが次々と飛び出してきて、そこに出力され、実行スタックがクリアされます。

実行スタックをクリアした後、実行エンジンはタスク キューのポーリングを継続して、実行可能なタスクがまだあるかどうかを確認します。

 5.webkit中timer的实现

  到这里已经可以彻底理解下面代码的执行流程,执行引擎先将setTimeout()方法入栈被执行,执行时将延时方法交给内核相应模块处理。引擎继续处理后面代码,while语句将引擎阻塞了1秒,而在这过程中,内核timer模块在0.5秒时已将延时方法添加到任务队列,在引擎执行栈清空后,引擎将延时方法入栈并处理,最终输出的时间超过预期设置的时间。

var start = new Date;
setTimeout(function(){
var end = new Date;
console.log(&#39;Time elapsed:&#39;, end - start, &#39;ms&#39;);
}, 500);
while (new Date - start < 1000) {};

  前面事件循环模型图中提到的WebAPIs部分,提到了DOM事件,AJAX调用和setTimeout方法,图中简单的把它们总结为WebAPIs,而且他们同样都把回调函数添加到任务队列等待引擎执行。这是一个简化的描述,实际上浏览器内核对DOM事件、AJAX调用和setTimeout方法都有相应的模块来处理,webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块,html的解析,css样式的计算等都由webcore实现。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现,这里还是继续以setTimeout为例,看下timer模块的实现。

  Timer类是webkit 内核的一个必需的基础组件,通过阅读源码可以全面理解其原理,本文对其简化,分析其执行流程。

setTimeout のイベント ループ モデルについて話しましょう

  通过setTimeout()方法注册的延时方法,被传递给webcore组件timer模块处理。timer中关键类为TheadTimers类,其包含两个重要成员,TimerHeap任务队列和SharedTimer方法调度类。延时方法被封装为timer对象,存储在TimerHeap中。和Java.util.Timer任务队列一样,TimerHeap同样采用最小堆的数据结构,以nextFireTime作为关键字排序。SharedTimer作为TimerHeap调度类,在timer对象到达触发条件时,通过浏览器平台相关的接口,将延时方法添加到事件循环模型中提到的任务队列中。

  TimerHeap采用最小堆的数据结构,预期延时时间最小的任务最先被执行,同时,预期延时时间相同的两个任务,其执行顺序是按照注册的先后顺序执行。

var start = new Date;
setTimeout(function(){
console.log(&#39;fn1&#39;);
}, 20);
setTimeout(function(){
console.log(&#39;fn2&#39;);
}, 30);
setTimeout(function(){
console.log(&#39;another fn2&#39;);
}, 30);
setTimeout(function(){
console.log(&#39;fn3&#39;);
}, 10);
console.log(&#39;start while&#39;);
while (new Date - start < 1000) {};
console.log(&#39;end while&#39;);

  上述代码输出依次为

start while
end while
fn3
fn1
fn2
another fn2

 参考资料

  1.《Javascript异步编程》

  2.JavaScript 运行机制详解:再谈Event Loophttp://www.ruanyifeng.com/blog/2014/10/event-loop.html

  3.Philip Roberts: Help, I'm stuck in an event-loop.https://vimeo.com/96425312

  4.How JavaScript Timers Work.http://ejohn.org/blog/how-javascript-timers-work/

  5.How WebKit’s event model works.http://brrian.tumblr.com/post/13951629341/how-webkits-event-model-works

  6.Timer实现.http://blog.csdn.net/shunzi__1984/article/details/6193023

以上がsetTimeout のイベント ループ モデルについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。