この記事では、ブラウザーのイベント ループについて詳しく説明します (コード例)。必要な方は参考にしていただければ幸いです。
ブラウザのイベント ループは、フロントエンドにとって非常に馴染みのあるものであり、毎日接触するものです。しかし、以前は暗記していました。 イベントタスクキューはマクロタスクとマイクロタスクに分かれており、ブラウザはまずマクロタスクからタスクを取り出して実行し、次にマイクロタスク内のすべてのタスクを実行してから、マクロタスクを実行してタスクを取り出します... . このサイクルが続きます。しかし、次のコードでは、setTimeout がマクロタスクに属しているため、最初に setTimeout を取り出して実行する必要がありますが、実行結果を見て私は混乱しました。
<script> setTimeout(() => { console.log(1) }, 0) new Promise((resolve) => { console.log(2) resolve() }).then(() => { console.log(3) }) // 我曾经的预期是:2 1 3 // 实际输出:2 3 1 </script>
他の人のタスクキューの紹介を注意深く読んだ後、同期的に実行されるjsコードが実際にはマクロタスクであることに気付きました(正確には、各scriptタグ内のコードがマクロタスクです)。ルールでは、 最初にマクロタスクを取り出して を実行しても問題ないとあります。
インターネット上の多くの記事では、上記のように説明されていますが、これは HTML のイベント ループの仕様だとずっと思っていて、それを覚えているだけです。最近 Li Yincheng 氏の記事 (記事の最後にある参考リンクを参照) を読んで初めて、以前に読んだ記事ではブラウザのマルチスレッド モデルを明確に分析していないことに突然気づきました。したがって、ブラウザのイベント ループは上記の規則に基づいて、実際にはブラウザのマルチスレッド モデルの結果であると考えられます。
マクロタスクは本質的に、ブラウザの複数のスレッド間で通信するためのメッセージ キューです。
Chrome では、各ページが 1 つのプロセスに対応します。 js スレッド、レンダリング スレッド、io スレッド、ネットワーク スレッド、タイマー スレッドなどの複数のスレッドが存在します。これらのスレッド間の通信は、相手のタスク キューにタスク (PostTask) を追加することで実現されます。
ブラウザ内のさまざまなスレッドは、for 無限ループで実行されます。各スレッドには独自のタスク キューがあり、タスク キューによってタスクが追加されます。 、これらのスレッドは実行のために独自のタスク キューから継続的にタスクを取り出したり、設定された時間までスリープしたり、PostTask 時に誰かがスレッドを起こしたりします。
単純に理解すると、ブラウザの各スレッドは常に自身のタスクキューからタスクを取り出しては実行し、再びタスクを取り出しては再度実行するという無限ループを繰り返していると言えます。
次のコードを例に挙げます。
<script> console.log(1) setTimeout(() => { console.log(2) }, 1000) console.log(3) </script>
まず、script タグ内のコードがタスクとして js スレッドのタスク キューに入れられます。 jsスレッドを起こしてタスクを取り出して実行します
最初にconsole.log(1)を実行し、次にsetTimeoutを実行し、タイマースレッドにタスクを追加して実行しますconsole.log(3) この時点で、js スレッドのタスク キューは空になり、js スレッドはスリープ状態になります。
約 1000 ミリ秒後に、タイマー スレッドがスケジュールされたタスクを追加します。 (タイマー コールバック) が js スレッドのタスク キューに追加され、js スレッドが再び起動され、スケジュールされたコールバック関数が実行され、最後に console.log(2) が実行されます。
ご覧のとおり、いわゆるマクロタスクは、ブラウザがどのタスクがマクロタスクであるかを定義することを意味するものではなく、ブラウザの各スレッドが独自のタスク キューを忠実に循環させるだけです。それは単なるタスクです。
ブラウザのマルチスレッド モデルによって引き起こされる「錯覚」であるマクロタスクと比較して、マイクロタスクは他のスレッドではなく、現在のスレッドに属するキューです。 PostTask。Promise.then や MutationObserver のように、タスクが遅延されるだけです (正確には、現在実行されている同期コードの後に実行されます)。
次のコードを例に挙げます。
<script> new Promise((resolve) => { resolve() console.log(1) setTimeout(() => { console.log(2) },0) }).then(() => { console.log(3) }) // 输出:1 3 2 </script>
まず、script タグ内のコードがタスクとして js スレッドのタスク キューに入れられます。 jsスレッドが起動され、タスクを取り出して
を実行し、新しいPromiseを実行してPromise内で解決すると、promiseのthenコールバック関数がタスクとして使用されます。これを遅延させて、現在実行されているすべての同期に配置する必要があります。コード
#を実行することです。
最後に、js スレッドのタスク キューが空になり、約 1000 ミリ秒後に js スレッドがスリープ状態になります。タイマー スレッドは、スケジュールされたタスク (タイマー コールバック) を js スレッドのタスク キューに追加し、js スレッドは終了します。再び目覚めて、スケジュールされたコールバック関数、つまり console.log(2) を実行します。通过上面的分析,可以看到,文章开头提到的规则:浏览器先从macrotask取出一个任务执行,再执行microtask内的所有任务,接着又去macrotask取出一个任务执行...,并没有说错,但这只是浏览器执行机制造成的现象,而不是说浏览器按照这样的规则去执行的代码。
这篇文章中的所有干货都来自李银成大佬的文章,我只是按照自己的理解,做了简化描述,方便大家理解,也加深自己的印象。
最后,看了这篇文章,大家能够基于浏览器的运行机制,分析出下面代码的执行结果了吗(ps:不要用死记硬背的规则去分析哟)
console.log('start') const interval = setInterval(() => { console.log('setInterval') }, 0) setTimeout(() => { console.log('setTimeout 1') Promise.resolve() .then(() => { console.log('promise 3') }) .then(() => { console.log('promise 4') }) .then(() => { setTimeout(() => { console.log('setTimeout 2') Promise.resolve() .then(() => { console.log('promise 5') }) .then(() => { console.log('promise 6') }) .then(() => { clearInterval(interval) }) }, 0) }) }, 0) Promise.resolve() .then(() => { console.log('promise 1') }) .then(() => { console.log('promise 2') }) // 执行结果 /* start promise 1 promise 2 setInterval setTimeout 1 promise 3 promise 4 setInterval setTimeout 2 promise 5 promise 6 */
以上がブラウザーのイベント ループの詳細 (コード例)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。