JavaScript
シングルスレッド言語、いわゆるシングルスレッドです
JS非同期メカニズム
JavaScript
これはシングルスレッド言語です。いわゆるシングルスレッドとは、一度に1つのタスクしか完了できないことを意味します。複数のタスクがある場合は、キューに入れる必要があります。前のタスクが完了してから、後者のタスクが実行されます。このモードの利点は、実装が比較的簡単で、実行環境が比較的単純なことです。欠点は、長時間かかるタスクがある限り、後続のタスクをキューに入れなければならず、プログラム全体の実行が遅延することです。一般的なブラウザは応答しません。これは誤った死の状態です。多くの場合Javascript
、無限ループなどの特定のコードが長時間実行され、ページ全体がこの場所で動かなくなり、他のタスクを実行できないためです。
実行メカニズム#
上記の問題を解決するためにJavascript
、タスクの実行モードは、同期Synchronous
と非同期Asynchronous
、同期または非同期の2つのタイプに分けられます。これは、プロセス全体を順番に完了する必要があるかどうか、ブロッキングまたはノンブロッキングを示します。つまり、呼び出す関数は実行しません。結果をすぐに教えて
同期#
同期モードは同期ブロッキングです。後者のタスクは、前のタスクの終了を待って実行します。プログラムの実行順序は、タスクの順序と一致しています。
var i = 100;
while(--i) { console.log(i); }
console.log("while 执行完毕我才能执行");
非同期#
非同期実行は非ブロッキングモードで実行されます。各タスクには1つ以上のコールバック関数がありますcallback
。前のタスクが終了すると、次のタスクを実行する代わりにコールバック関数が実行されます。後者のタスクは、前のタスクの終了を待たずに実行されます。 、したがって、プログラムの実行順序とタスクの順序には一貫性がなく、非同期です。ブラウザTab
は、それぞれJs
に1つのスレッドのみを割り当てます。主なタスクは、ユーザーと対話して操作DOM
することなどです。これにより、1つのスレッドしかできないと判断されます。それ以外の場合は、非常に複雑な同期の問題が発生します。たとえば、JavaScript
2つのスレッドがあると想定します、スレッドDOM
が特定のノードにコンテンツを追加し、別のスレッドがノードを削除すると、ブラウザはどのスレッドの操作が優先されるかを判断できません。
setTimeout(() => console.log("我后执行"), 0); // 注意:W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms,此外这与浏览器设定、主线程以及任务队列也有关系,执行时间可能大于4ms,例如老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动尤其是涉及页面重新渲染的部分,通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。 console.log("我先执行");
非同期メカニズム#
最初に例を見て、上記のように非同期に実行される操作をテストします
setTimeout(() => console.log("我在很长时间之后才执行"), 0); var i = 3000000000; while(--i) { } console.log("循环执行完毕");
ローカルテストでは、setTimeout
コールバック関数セット30s
がaboutの後に実行されます4ms
。これはそれよりはるかに大きくなります。Js
メインスレッドに非常に大きなループを設定して、メインスレッドをブロックします。ここでは、無限ループを設定していません。ここで無限ループを設定した場合メインスレッドをブロックするために、setTimeout
コールバック関数は実行されません。また、レンダリングスレッドとJS
エンジンスレッドは相互に排他的であるためJs
、スレッドがタスクを処理している間、レンダリングスレッドは中断され、ページ全体がブロックされ、更新できません。ページを閉じることはできません。ページを閉じるには、タスクマネージャを使用してTab
プロセスを終了する必要があります。Js
非同期は、実行スタックとタスクキューを通じて実現され、非同期操作を完了します。すべての同期タスクはメインスレッドで実行され、実行スタックを形成します。タスクキューには、さまざまなイベントコールバック(メッセージとも呼ばれます)が格納されます。実行スタックでのタスク処理が完了すると、メインスレッドがタスクキュー内のタスクの読み取りを開始して実行し、ループを継続的に往復させます。
たとえば、上記の例のsetTimeout
完了したイベントコールバックはタスクキューに格納されます。エンジンはシングルスレッドJavaScript
であるため、ブラウザタイマーカウンターはエンジンによってカウントされないことに注意してくださいJavaScript
。スレッドがブロックされている場合、タイミングに影響します。正確には、カウントはブラウザスレッドによってカウントされます。カウントが完了すると、イベントコールバックがタスクキューに追加されます。同様HTTP
に、ブラウザには別のスレッドがあり、イベントコールバックは実行後にタスクキューに配置されます。このプロセスを通じてsetTimeout
、上記の例のコールバックが実行できなかった理由を説明できます。これは、メインスレッド、つまり実行スタックのコードが完了していないため、このイベントコールバックがすでに実行されている場合でも、実行するタスクキューのイベントコールバックを読み取らないためです。タスクキュー。
イベントループ#
メインスレッドがタスクキューイベントから読み込んで、プロセスは常にので、このメカニズムの全体的な動作も知られ、循環されEvent Loop
、Event Loop
別の場所で異なる方法で実装エグゼクティブモデルであり、ブラウザとNodeJS
異なる技術に基づいて実装します彼ら自身のEvent Loop
。ブラウザは、Event Loop
でありHTML5
、明確に定義された仕様に基づいて実装。ブラウザの実行スタック、バックグラウンドスレッド、マクロキュー、マイクロキューで構成されます。NodeJS
Event Loop
libuv
Event Loop
Execution Stack
Background Threads
Macrotask Queue
Microtask Queue
- 実行スタックは、メインスレッドで同期タスクを実行するデータ構造であり、関数呼び出しは複数のフレームで構成されるスタックを形成します。
- バックグラウンドスレッドを実現するためのブラウザで
setTimeout
、setInterval
、XMLHttpRequest
および実行のスレッドでそう。 - マクロキュー、非同期タスクのいくつかは、フォローアップと呼ばれるのを待って、マクロコールバックキューに変わります、を含む
setTimeout
、setInterval
、setImmediate(Node)
、requestAnimationFrame
、UI rendering
、I/O
およびその他の操作 - Microqueue、他の非同期タスクには、フォローアップコールを待って、マイクロコールバックキューに変わります
Promise
、process.nextTick(Node)
、Object.observe
、MutationObserver
およびその他の操作
ときにJs
実行され、次のプロセス
- まず、実行スタック内のコードが同期的に実行され、これらのコード内の非同期タスクがバックグラウンドスレッドに追加されます
- 実行スタック内の同期コードが実行された後、実行スタックが空になり、マイクロキューがスキャンされます
- マイクロキューの最初のタスクを実行スタックに入れて実行します。このとき、マイクロキューはデキューされます
- 実行スタックが完了したら、micro queueタスクがすべて実行されるまで、micro queueタスクのデキューと実行を続けます。
- 最後のマイクロキュータスクがデキューされて実行スタックに入ると、マイクロキュー内のタスクは空になります。実行スタックタスクが完了すると、マイクロキューはサーフェスのスキャンを開始します。マクロキュータスクのスキャンを続行します。実行が途中で、実行が完了した後、microキューが空であるのをスキャンし続け、次にマクロキューをスキャンし、実行をデキューします。
- 行ったり来たりして......
例#
// Step 1
console.log(1);
// Step 2
setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3); }); }, 0); // Step 3 new Promise((resolve, reject) => { console.log(4); resolve(); }).then(() => { console.log(5); }) // Step 4 setTimeout(() => { console.log(6); }, 0); // Step 5 console.log(7); // Step N // ... // Result /* 1 4 7 5 2 3 6 */
ステップ1
// 执行栈 console
// 微队列 []
// 宏队列 []
console.log(1); // 1
ステップ2
// 执行栈 setTimeout
// 微队列 []
// 宏队列 [setTimeout1]
setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3); }); }, 0);
ステップ3
// 执行栈 Promise
// 微队列 [then1]
// 宏队列 [setTimeout1]
new Promise((resolve, reject) => { console.log(4); // 4 // Promise是个函数对象,此处是同步执行的 // 执行栈 Promise console resolve(); }).then(() => { console.log(5); })
ステップ4
// 执行栈 setTimeout
// 微队列 [then1]
// 宏队列 [setTimeout1 setTimeout2]
setTimeout(() => { console.log(6); }, 0);
手順5
// 执行栈 console
// 微队列 [then1]
// 宏队列 [setTimeout1 setTimeout2]
console.log(7); // 7
手順6
// 执行栈 then1
// 微队列 []
// 宏队列 [setTimeout1 setTimeout2]
console.log(5); // 5
手順7
// 执行栈 setTimeout1
// 微队列 [then2]
// 宏队列 [setTimeout2]
console.log(2); // 2 Promise.resolve().then(() => { console.log(3); });
手順8
// 执行栈 then2
// 微队列 []
// 宏队列 [setTimeout2]
console.log(3); // 3
手順9
// 执行栈 setTimeout2
// 微队列 []
// 宏队列 []
console.log(6); // 6
参照番号
https://www.jianshu.com/p/1a35857c78e5
https://segmentfault.com/a/1190000016278115
https://segmentfault.com/a/1190000012925872 https://www.cnblogs.com/sunidol/p/11301808.html http://www.ruanyifeng.com/blog/2014/10/event-loop.html https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop