ホームページ  >  記事  >  ウェブフロントエンド  >  なぜ非同期が発生するのでしょうか? イベントキューとは何ですか?

なぜ非同期が発生するのでしょうか? イベントキューとは何ですか?

PHP中文网
PHP中文网オリジナル
2017-06-21 13:26:273215ブラウズ

はじめに

JavaScript を勉強したことのある人なら誰でも、JavaScript がシングルスレッド言語であることを知っていると思います。つまり、JS はマルチスレッド プログラミングを実行できませんが、JS にはユビキタスな 非同期 の概念があります。初期の頃、多くの人は非同期をマルチスレッドと同様のプログラミング モデルとして理解していましたが、実際、非同期を完全に理解するには、JS の実行コアである イベント ループ を理解する必要があります。私はこれまでイベント ループについてあまり理解していませんでしたが、Philip Roberts の講演「そもそもイベント ループとは何ですか?」を見て初めて、イベント ループについて包括的に理解したので、概要を書きたいと思いました。 JS イベント ループの記事は、誰でも学習して参照できるようになります。

1. 非同期が発生するのはなぜですか?

なぜJSには非同期性があるのでしょうか?コードを同期的に実行すると何が起こるかを想像してみましょう:

1 $.get(url, function(data) {2     //do something3 });

ajax を使用して通信するとき、誰もがデフォルトで非同期に設定しますが、同期実行に設定するとどうなるでしょうか?自分で小さなテスト プログラムを作成し、バックグラウンド コードを 5 秒間遅らせると、ブラウザは ajax が応答するまでブロックされ、その後通常どおり実行されることがわかります。これは、非同期モードで解決する必要がある主な問題、つまりブラウザーにノンブロッキングでタスクを実行させる方法です。 ajax リクエストを同期的に実行すると、ネットワーク通信の待ち時間は非常に速い場合もあれば、非常に遅い場合もあり、また、未知のミッションでブラウザがブロックされる可能性もあります。 、これは私たちが見たくないものです。したがって、プログラムを非同期に処理する方法があることを望みます。ajax リクエストがいつ完了するかを気にする必要はなく、応答が返された後にリクエストを処理する方法だけを知っていればよいのです。この間に他の作業を行うこともできます。したがって、JavaScript イベント ループです。

2. イベントキューとは何ですか?

まず、簡単なコードを見てみましょう:

1 console.log("script start");2 3 setTimeout(function () {4     console.log("setTimeout");5 }, 1000);6 7 console.log("script end");

結果はここで見ることができます:

まず第一に、プログラムが'script start''script endを出力していることがわかります。約1秒後に', 'setTimeout'が出力されました。プログラムの 'script end' は 1 秒の出力を待たずに、すぐに出力します。これは、setTimeout が非同期関数であるためです。これは、遅延関数を設定すると、現在のスクリプトはブロックされず、ブラウザーのイベント テーブルに記録されるだけで、プログラムは実行を継続することを意味します。遅延時間が経過すると、イベントテーブルはイベントキュー(タスクキュー)にコールバック関数を追加し、イベントキューがタスクを取得した後、そのタスクを実行スタック(スタック)にプッシュします。実行スタックはタスクを実行します 'setTimeout'

イベントキューは、実行されるタスクを格納するキューであり、タスクは厳密に時系列順に実行され、キューの先頭にあるタスクが最初に実行され、キューの最後にあるタスクが最後に実行されます。 。イベント キューは一度に 1 つのタスクのみを実行します。タスクが完了すると、次のタスクが実行されます。実行スタックは、関数呼び出しスタックに似た実行中のコンテナです。実行スタックが空の場合、JS エンジンはイベント キューをチェックし、空でない場合は、イベント キューが最初のタスクを実行スタックにプッシュします。

さて、上記のコードに少し変更を加えてみましょう:

1 console.log("script start");2 3 setTimeout(function () {4     console.log("setTimeout");5 }, 0);6 7 console.log("script end");

遅延時間を 0 に設定し、プログラムがどのような順序で出力されるかを確認してください。遅延時間をどれだけ設定しても、'setTimeout'は常に 'script end'の後に出力されます。ブラウザによっては最小遅延時間が設定されており、15 ミリ秒、10 ミリ秒のブラウザもあります。これは多くの本で言及されており、学生に錯覚を与える可能性があります。プログラムは非常に高速に実行され、最小遅延時間が設定されているため、 'setTimeout' 'script end'の後に出力されます。ここで、あなたの誤解を払拭するために少し変更してみましょう:

 1 console.log("script start"); 2  3 setTimeout(function () { 4     console.log("setTimeout"); 5 }, 0); 6  7 //具体数字不定,这取决于你的硬件配置和浏览器 8 for(var i = 0; i < 999999999; i ++){ 9     //do something10 }11 12 console.log("script end");

  你可以在这里查看结果:


   可以看出,无论后面我们做了多少延迟性的工作,'setTimeout' 总是会在 'script end' 之后输出。所以究竟发生了什么?这是因为 setTimeout 的回调函数只是会被添加至事件队列,而不是立即执行。由于当前的任务没有执行结束,所以 setTimeout 任务不会执行,直到输出了 'script end' 之后,当前任务执行完毕,执行栈为空,这时事件队列才会把 setTimeout 回调函数压入执行栈执行。


  执行栈则像是函数的调用栈,是一个树状的栈:


 三、事件队列有何作用?

  通过以上的 demo 相信同学们都会对事件队列和执行栈有了一个基本的认识,那么事件队列有何作用?最简单易懂的一点就是之前我们所提到的异步问题。由于 JS 是单线程的,同步执行任务会造成浏览器的阻塞,所以我们将 JS 分成一个又一个的任务,通过不停的循环来执行事件队列中的任务。这就使得当我们挂起某一个任务的时候可以去做一些其他的事情,而不需要等待这个任务执行完毕。所以事件循环的运行机制大致分为以下步骤:

  1.   检查事件队列是否为空,如果为空,则继续检查;如不为空,则执行 2;

  2.   取出事件队列的首部,压入执行栈;

  3.        执行任务;

  4.        检查执行栈,如果执行栈为空,则跳回第 1 步;如不为空,则继续检查;

  然而目前为止我们讨论的仅仅是 JS 引擎如何执行 JS 代码,现在我们结合 Web APIs 来讨论事件循环在当中扮演的角色。

  在开始我们讨论过 ajax 技术的异步性和同步性,通过事件循环机制,我们则不需要等待 ajax 响应之后再进行工作。我们则是设置一个回调函数,将 ajax 请求挂起,然后继续执行后面的代码,至于请求何时响应,对我们的程序不会有影响,甚至它可能永远也不响应,也不会使浏览器阻塞。而当响应成功了以后,浏览器的事件表则会将回调函数添加至事件队列中等待执行。事件监听器的回调函数也是一个任务,当我们注册了一个事件监听器时,浏览器事件表会进行登记,当我们触发事件时,事件表便将回调函数添加至事件队列当中。


  我们知道 DOM 操作会触发浏览器对文档进行渲染,如修改排版规则,修改背景颜色等等,那么这类操作是如何在浏览器当中奏效的?至此我们已经知道了事件循环是如何执行的,事件循环器会不停的检查事件队列,如果不为空,则取出队首压入执行栈执行。当一个任务执行完毕之后,事件循环器又会继续不停的检查事件队列,不过在这间,浏览器会对页面进行渲染。这就保证了用户在浏览页面的时候不会出现页面阻塞的情况,这也使 JS 动画成为可能, jQuery 动画在底层均是使用 setTimeout 和 setInterval 来进行实现。想象一下如果我们同步的执行动画,那么我们不会看见任何渐变的效果,浏览器会在任务执行结束之后渲染窗口。反之我们使用异步的方法,浏览器会在每一个任务执行结束之后渲染窗口,这样我们就能看见动画的渐变效果了。

  考虑如下两种遍历方式:

 1 var arr = new Array(999); 2 arr.fill(1); 3 function asyncForEach(array, handler){ 4     var t = setInterval(function () { 5         if(array.length === 0){ 6             clearInterval(t); 7         }else { 8             handler(arr.shift()); 9         }10     }, 0);11 }12 13 //异步遍历14 asyncForEach(arr, function (value) {15     console.log(value);16 });17 18 //同步遍历19 arr.forEach(function (value, index, arr) {20     console.log(value);21 });

テスト後、同期トラバーサル方式を使用すると、配列の長さが 3 桁に達するとブロックが発生しますが、非同期トラバーサルではブロックされないことがわかります (配列の長さが非常に大きくない限り、コンピューターには十分なメモリ容量があります)。これは、同期走査メソッドが別のタスクであるため、このタスクは次のタスクを開始する前にすべての配列要素を走査するためです。非同期トラバーサル メソッドは、各トラバーサルを個別のタスクに分割します。各タスクは 1 つの配列要素のみをトラバースするため、各タスク間でブラウザがレンダリングできるため、ブロックは発生しません。次のデモは、非同期トラバーサルの前後で何が起こるかを示しています:


概要

さて、JavaScript の本当の姿を理解できたと思います。 JavaScript はシングルスレッド言語ですが、そのイベント ループ機能によりプログラムを非同期で実行できます。これらの非同期プログラムは、setTimeout、setInterval、ajax、eventListener などの独立したタスクを次々と実行します。イベント ループに関しては、次の点に注意する必要があります:

  • イベント キューはタスクを厳密な時系列順に実行スタックにプッシュします

  • 実行スタックが空の場合、ブラウザはイベント キューをチェックし続けます。空でない場合は、最初のタスクを取り出します

  • 各タスクが終了すると、ブラウザがページをレンダリングします

この記事のデモは、jsfiddle に置かれますので、ご注意ください。ただソースを明らかにしてください。この記事に間違いを見つけた場合は、コメント欄で指摘してください。

以上がなぜ非同期が発生するのでしょうか? イベントキューとは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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