導入
今日のインターネット時代では、私たちはブラウザを使用してさまざまな Web ページやアプリケーションにアクセスすることがよくあります。しかし、Web ページでトリガーされるさまざまなイベントやタスクをブラウザーがどのように処理して実行するかについて考えたことがありますか? これには、ブラウザのメッセージ キューとイベント ループ メカニズムが関係します。
複雑なソフトウェア システムであるブラウザーは、ユーザーが Web ページやアプリケーションをスムーズに使用できるように、さまざまなタスクを効率的に管理および実行する必要があります。メッセージ キューとイベント ループ メカニズムは、ブラウザーがこれらのタスクを処理するために使用するコア メカニズムです。
この記事では、ブラウザのメッセージ キューとイベント ループのメカニズムを詳しく調べ、マクロタスクとマイクロタスクの概念について学びます。メッセージ キューがタスクを編成してスケジュールする方法と、イベント ループがタスクの実行順序を調整する方法について詳しく学習します。これらのメカニズムを深く理解することで、Web ページとアプリケーションをより適切に最適化し、ユーザー エクスペリエンスを向上させることができます。それでは、ブラウザのメッセージ キューとイベント ループのメカニズムを調べてみましょう。
本文を始める前に、質問について考えてみましょう。
问题:js是单线程的,那它是如何同时处理多个任务的呢?
JavaScript がシングルスレッドであるにもかかわらず、複数のタスクを同時に処理できるのは、JavaScript が非同期プログラミング モデルを使用しているためです。非同期プログラミングを使用すると、JavaScript はタスクが完了するまでプログラムの実行をブロックすることなく、他のタスクの実行を継続できます。時間のかかる操作 (ネットワーク リクエストやファイルの読み書きなど) が発生した場合、JavaScript はこれらのタスクをブラウザの他のスレッドに委任し、メイン スレッドが引き続き他のタスクを実行できるようにします。
JavaScript は、コールバック関数、イベント リスナー、Promise (Promise) などのメカニズムを使用して、非同期タスクを処理します。非同期タスクが完了すると、コールバック関数またはイベントを起動するか、Promise オブジェクトを返して、タスクの完了後もコードの実行を継続できるようにします。
たとえば、ネットワーク リクエストが行われると、JavaScript はそのリクエストをブラウザのネットワーク スレッドに送信し、その背後でコードの実行を続けます。ネットワーク リクエストが完了すると、ブラウザは結果を JavaScript に返し、コールバック関数またはイベントを通じてコードに通知します。この非同期処理メカニズムにより、JavaScript はネットワーク要求を待機している間も他のタスクの処理を続行できるため、プログラムのパフォーマンスとユーザー エクスペリエンスが向上します。
JavaScript はシングルスレッドですが、非同期プログラミング モデルを通じて複数のタスクを同時に処理できるため、複数のタスクを処理することができます。
1. ブラウザのメッセージキューとイベントループの仕組み
1。概要
ブラウザーのメッセージ キューとイベント ループ メカニズムは、非同期タスクを処理する方法です。ブラウザは、ネットワーク リクエスト、ユーザー インタラクションなど、複数のタスクを同時に処理できる必要があります。タスクのブロックを回避するために、ブラウザはメッセージ キューを使用してタスクの実行順序を管理し、イベント ループ機構を通じてタスクの実行を処理します。
動作原理を次の図に示します。
2. メッセージキュー
メッセージ キュー: ブラウザには、保留中のタスクを保存するためのメッセージ キュー (タスク キューとも呼ばれます) が用意されています。非同期タスクが完了すると、実行のためにメッセージ キューに追加されます。
ブラウザのメッセージ キューは、保留中のイベントを管理するためのメカニズムです。その役割は、さまざまなトリガー イベント (ユーザー インタラクション、ネットワーク リクエスト、タイマーなど) をキューに追加し、それらを先入れ先出しの順序で順番に実行することです。
具体的なプロセスは次のとおりです。
- ユーザーがボタンをクリックするなど、ブラウザーでイベントが発生すると、ブラウザーはそのイベントをメッセージ キューに追加します。
- メッセージ キューはイベントを順序付けし、追加された順序で処理します。
- イベント ループはメッセージ キューからイベントを取得し、処理のために実行コンテキストに送信します。
- 実行コンテキストは、イベントの種類と登録されたコールバック関数に応じて、対応する操作を実行します。
- イベント ループはメッセージ キューから次のイベントをフェッチし続け、メッセージ キューが空になるまで上記のプロセスを繰り返します。
ブラウザはメッセージ キューを通じて、イベントの順序と非同期処理機能を保証できます。このメカニズムにより、長時間実行されるタスクによって UI の応答性が妨げられることがなくなり、よりスムーズなユーザー エクスペリエンスが提供されます。
3. イベントループ
イベント ループとは、ブラウザがメッセージ キューからタスクを継続的に取得して実行するプロセスを指します。ブラウザは、メッセージ キューにタスクがあるかどうかを周期的に継続的にチェックします。メッセージ キューが空でない場合、ブラウザはキュー内の最初のタスクを取り出し、キューが空になるまでそれを実行します。メッセージ キューが空の場合、ブラウザはスリープ状態になり、新しいタスクが参加するのを待ちます。
ブラウザのイベント ループ メカニズムは、メッセージ キューからタスクを取り出し、これらのタスクを JavaScript エンジンに渡して実行する役割を果たします。各イベントループの実行プロセスをイベントフレームと呼びます。
イベント ループの基本的な流れは次のとおりです。
- メッセージキューからタスクを取得します。
- 根据任务的类型,将其交给相应的处理机制处理。例如,如果是用户交互产生的事件,会触发相应的事件处理函数。
- 处理完一个任务后,判断是否需要更新页面渲染。
- 如果需要更新渲染,则执行渲染操作。
- 重复以上步骤,进行下一个事件帧的循环。
举例说明:
假设有以下代码:
console.log('1');
setTimeout(function() {
console.log('2');
}, 0);
console.log('3');
执行过程如下:
- 打印 “1”。
- 遇到
setTimeout
,将回调函数放入消息队列中,并设置延时为0。 - 打印 “3”。
- 事件循环开始,从消息队列中取出第一个任务(即
setTimeout
的回调函数),并执行。 - 打印 “2”。
通过上述示例可以看出,即使 setTimeout
的延时设为0,仍然会在打印 “3” 之后才执行回调函数。这是因为在执行过程中,浏览器首先会执行同步任务(如打印 “1” 和 “3”),然后再从消息队列中取出下一个任务执行。
总结:
消息队列和事件循环机制使得浏览器能够在处理异步任务时实现非阻塞的方式。事件循环不断地从消息队列中取出任务并执行,从而保证了任务的按序执行。
二、浏览器中的宏任务(Macro task)和微任务(Micro task)
浏览器在解析JavaScript代码时,使用宏任务和微任务队列是为了能够正确地处理异步操作和优化任务执行顺序。
在事件循环中,任务可以被分为宏任务和微任务两种类型:
- 宏任务(
macro task
):包括但不限于setTimeout、setInterval、I/O
操作等。 - 微任务(
micro task
):包括Promise
的回调函数、MutationObserver
的回调函数等。
通过将异步操作区分为宏任务和微任务,浏览器可以更好地管理事件循环。事件循环是浏览器运行JavaScript的机制,在事件循环中,浏览器会从宏任务队列中取出一个任务执行,然后执行微任务队列中的所有任务,直到微任务队列为空。然后再取出一个宏任务执行,依此往复。
这种机制可以保证异步操作按照正确的顺序执行,并能够优先处理微任务,这有助于提高页面的响应性能和用户体验。同时,由于微任务执行时机早,可以在下一个宏任务之前进行一些重要的操作,比如对DOM
进行修改,这样可以避免一些不必要的重绘和回流操作,提升性能。
这种机制保证了微任务的优先级高于宏任务,因此微任务的执行顺序会优先于宏任务。
总之,宏任务和微任务队列的设计是为了保证异步操作的顺序和性能,提供更好的用户体验。
上图展示了浏览器在解析JavaScript
时的消息队列和事件循环机制,同时显示了事件循环中的宏任务和微任务。以下是解释:
-
当浏览器解析网页时,首先执行宏任务
Fetch
,通常是从服务器获取所需资源。这个过程可能需要花费几天的时间。 -
接着是宏任务
Script
,这是执行 JavaScript 代码的阶段,通常只需要几毫秒或几秒的时间。 -
在
Script
任务期间,如果有微任务(例如Promise
的回调函数、MutationObserver
等)被注册,它们将被添加到微任务队列中。 -
当
Script
任务执行完毕后,事件循环将检查微任务队列是否为空。如果不为空,将按照先进先出(FIFO)的顺序立即执行所有微任务。 -
宏任务
Layout
表示对DOM
进行重新布局,通常包括计算元素在页面中的位置、尺寸等。这个过程可能需要几天的时间。 -
宏任务
Paint
标识绘制页面元素颜色和样式,通常包括页面渲染、绘制元素等。这个过程可能需要几天的时间。 -
最后,宏任务
Composite
表示将已渲染的页面元素组合成最终的可见图像。这个过程可能需要一天的时间。
这样,整个事件循环过程就完成了一次循环。需要注意的是,宏任务可以有多个,它们按照顺序执行,但是微任务将在每个宏任务完成后立即执行,不会被插入宏任务之间。
浏览器的宏任务和微任务执行机制如下:
解释如下:
-
浏览器中的任务分为宏任务和微任务两种类型。
-
宏任务(
Macro task
)通常包括整体代码script、setTimeout、setInterval、I/O、UI
渲染等,它们按照顺序依次执行。 -
微任务(
Micro task
)主要包括Promise、MutationObserver
等,它们的执行在宏任务之间执行。 -
当执行顺序到达宏任务时,它们会被放入宏任务队列(
Macro task queue
)中等待执行。 -
当执行顺序到达微任务时,它们会被放入微任务队列(
Micro task queue
)中等待执行。 -
在执行宏任务前,先执行完微任务队列中的所有微任务。当一个宏任务执行完后,会查看微任务队列中是否存在微任务,如果有,则继续执行微任务。
举个例子来说明:
console.log('1');
setTimeout(function() {
console.log('2');
Promise.resolve().then(function() {
console.log('3');
});
});
Promise.resolve().then(function() {
console.log('4');
});
console.log('5');
在这个例子中,首先会输出’1’,因为它是在第一个宏任务中直接执行的。然后,遇到了一个 setTimeout,它是一个宏任务,所以会被加入到宏任务队列,并继续执行后面的语句,输出’5’。当第一个宏任务执行完毕后,会从宏任务队列中取出下一个宏任务,这时候会先执行微任务队列中的任务,输出’4’。紧接着,会执行 setTimeout 的回调函数,输出’2’,并且立即创建一个微任务并加入微任务队列,输出’3’。最后,宏任务队列中没有任务了,完成整个事件循环的执行过程。
因此,上述代码的输出结果为:
1
5
4
2
3
综上所述,该示例中的宏任务和微任务依次按照顺序执行,保持了JavaScript的单线程特性。
总结
总结起来,浏览器的消息队列和事件循环机制是保证 JavaScript
代码执行顺序的关键。在浏览器中执行的代码分为宏任务和微任务,宏任务包括整体的script
代码、setTimeout
、setInterval
等,而微任务则包括 Promise、MutationObserver
等。
事件循环机制的原理是不断从宏任务队列中取出一个任务执行,然后检查微任务队列是否有任务需要执行,如果有,则一直执行微任务队列中的任务,直到微任务队列为空。这样的机制保证了 JavaScript 代码能够按照预期的顺序执行,同时还能处理异步任务和事件回调。
了解浏览器的消息队列和事件循环机制对于开发者来说是非常重要的,它有助于我们优化代码和处理异步操作,避免意外的行为和错误。在编写 JavaScript
代码时,我们应该合理地使用宏任务和微任务,避免堵塞主线程,提高页面的渲染性能和响应能力。
希望通过这篇介绍,读者们能够更好地理解浏览器的消息队列和事件循环机制,能够更加灵活地编写 JavaScript
代码,提高开发效率和用户体验。