序文
JS固有の非ブロックシングルスレッドのスクリプト言語。言語は、単一のスレッドとして、メインスレッドは、1つのタスクのみJSコード実行を実行します。
ノンブロッキング実装では、我々が話をしたいのイベントループに依存しています。イベントループ仕様はちょうど理解することが、本当に苦いと難しいですが、私はかむのは難しいことはお勧めしません。
プロセスとスレッド
jsがシングルスレッドの言語で言って。だからのスレッドが何であるか、フロントエンドの学生のほとんどのために、それはあまり明確ではないかもしれません。推奨この記事のグエン首長、鮮やかな
すべての最初は、それはすべてのコンピューティングタスクを想定し、コンピュータのCPUのコアです。それは常に動作して、工場のようなものです。
プロセス
工場の床のようなプロセスは、それが単一のCPUがタスクを処理することができ表します。
いずれかの時点で、CPUは常にプロセスを実行し、他のプロセスが実行されていません。
すなわち、リソース割り当ての最小単位、独立スタック領域とデータ記憶領域であります
スレッド
ワークショップでのスレッドのような労働者の比率。ワークショップのスペースが共有されているプロセスのメモリ空間を象徴する、労働者によって共有され、各スレッドは、これらの共有メモリを使用することができます。
つまり、プログラムの実行、複数のスレッドを含むことができ、処理の最小単位です。
スイッチングは、より効率的かつ少ないオーバーヘッドであるように、プロセスに関しては、スレッドは、オペレーショナル・データ・スペースを必要としません。
Jsのシングルスレッドの原点
明らかに、並列処理で複数のプロセスがCPU使用率を向上させます。
初期のjsのスクリプトが表示される。しかし、それはユーザへの表示を完了するために、DOMと対話しなければなりません。
もし複数のプロセス、DOMの同時操作、制御できないの、その後の結果。
例えば:同じボタンは、異なるプロセスがどのように表示最後に、異なる色を与え。
それはJSので、あなたが複数のスレッド+ロック、そしてあまりにも複雑を、使用している場合はスクリプト言語としては、シングルスレッド。
しかし、開発のJSで、単一のスレッドに、より多くの限られたを運ぶ能力はJS効率およびその他の制限が適用される場合があります。
これにより、非DOM操作を実行するには、Webワーカーを増やします。
しかし、非メインスレッドのスレッドがいくつかの制限があり、例えば、DOMなどを操作することはできません、それは彼らが心配ではないでしょうDOM操作の一貫性を確保するために、あります。
当社の能力または非ブロックの基礎の主な焦点は、それは、イベントループです。
イベントループブラウザ
彼はイベントループはイベントキューを開始すると発表しました。
メインスレッドの実行では、ヒープ(ヒープ)とスタック(スタック)を生成します。
ヒープメモリは、我々は、スタックメモリ空間を実行するための基本的なデータ型や関数を実行することです宣言されたデータのオブジェクトタイプです。
メインスレッドは、タスクキューのイベント、プロセスを絶えず循環しているので、イベントループで、この操作機構から読み込みます。
同期コードを直接実行されます。
また、別の方法は、非同期イベントキューを実行するときに追加しましたが、違いがある非同期イベントが、違いは、優先順位の実装であることになります。
イベントカテゴリ
それは非同期タスク間で同じではないので、それらの実行優先度が異なるため。マイクロタスク(マイクロタスク)とマクロタスク(マクロタスク):異なる非同期タスクは、2つのカテゴリに分けられます。
- 次のイベントは、マクロタスクました:
setTimeout、のsetInterval、setImmediate、I / O、UIのレンダリング
- 次のイベントは、マイクロタスクました
約束、Object.observe(廃止)、MutationObserver(HTML5の新機能)、process.nextTick
前に実行され、実行スタックコード(同期タスク)、常に「タスクキュー」を読んで(非同期タスク)
現在の実行スタックが終了したイベントキューにあるすべてのマイクロタスクを処理するために、すぐには、マクロタスクキューに行きますイベントを削除します。同一のイベントループで、マイクロマクロタスクは常にタスクの前に実行されます。
次のようにさまざまな種類のタスクの実行順序の場合:
- 同期コードの実行
- イベントループ開始
- マイクロタスクはキュー(実行)を空にし始めました
- タスクが空かどうか、4がスキップチェックし、何の6をスキップしません
- キューからタスクを抽出するタスクは、実行します
- マイクロタスクは空、ノースキップ3場合は2をスキップするかどうかを確認します
- イベントループの終わり
おそらく、チャートを流し、次のとおりです。
直接として栗を参照してください。
setTimeout(function () {
console.log(1);
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val);
})
// 2 3 1
- イベントの種類を区別:マクロタスクのsetTimeout、マイクロタスクを.then
- 2つの出力同期コードが実行されます
- タスクキュー空マイクロ出力3
- マクロタスク実行出力1
もう少し複雑に次のとおりです。
setTimeout(()=>{
console.log('A');
},0);
var obj={
func:function () {
setTimeout(function () {
console.log('B')
},0);
return new Promise(function (resolve) {
console.log('C');
resolve();
})
}
};
obj.func().then(function () {
console.log('D')
});
console.log('E');
// c,e,d,b,a
あなたは例を挙げてそれを自分で試すことができます。
イベントループ機構ノードで
ノードにおいて、イベントループは、ほぼ同じブラウザの状態を表示します。違いは、ノードは、独自のモデルを持っているということです。
イベントループ内のノードを達成することはlibuvエンジンに依存することです。
私たちは、そのノードが、JS通訳として対応するノードを呼び出すためのV8エンジンjsのコード解析APIをクロームV8エンジンを選択、知っている
とlibuvエンジンドライバによってこれらの最後のAPI、対応するタスクを実行し、異なる上の異なるイベントメインスレッドのキュー待機。
だから、実際にはイベントループノードはlibuvエンジンに存在しています。
ノードのイベントは、次の主要な段階に分かれています。
- タイマー:実装のsetTimeout()としたsetInterval()のコールバックセットのこのフェーズ。
- I / Oコールバック:コールバック、タイマーコールバック、およびsetImmediate()コールバックを閉じるに加えて、ほぼすべてのコールバックを行います。
- アイドル、準備:内部使用のみ。
- 世論調査:新しいI / Oイベントを取得し、ノードは、新しいI / Oのを待って、適切な条件の下でここまで戻ってきます
- チェック:プールフェーズの後、実行setImmediateは()コールバックを設定します。
- クローズコールバック:このようsocket.onの実装(「近い」、...)コールバック
投票相
特別な注意その価値は投票段階
ステージは、以下の機能があります。
- 実行タイマーは、ミッションの時間制限の段階に達します。
- ポーリングタスクキューの実装フェーズ。
あなたは投票段階、および参加するノータスクタイマー段階に入る場合は、以下が起こります
- 如果 poll 队列不为空的话,会执行 poll 队列直到清空或者系统回调数达到上限
- 如果 poll 队列为空 如果设定了 setImmediate 回调,会直接跳到 check 阶段。 如果没有设定 setImmediate 回调,会阻塞住进程,并等待新的 poll 任务加入并立即执行。
process.nextTick()
nextTick 比较特殊,它有自己的队列,并且,独立于event loop。 它的执行也非常特殊,无论 event loop 处于何种阶段,都会在阶段结束的时候清空 nextTick 队列。
直接看例子吧:
process.nextTick
process.nextTick(function A() {
console.log(1);
process.nextTick(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED
大概顺序如下:
- 因为nextTick的特殊性,当前阶段执行完毕,就执行。所以直接,输出1 2
- 执行到timer 输出 TIMEOUT FIRED
setImmediate
setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
这个结果不固定,同一台机器测试结果也有两种:
// TIMEOUT FIRED =>1 =>2
或者
// 1=>TIMEOUT FIRED=>2
- 事件队列进入timer,性能好的 小于1ms,则不执行回调继续往下。若此时大于1ms, 则输出 TIMEOUT FIRED 就不输出步骤3了。
- poll阶段任务为空,存在setImmediate 直接进入setImmediate 输出1
- 然后再次到达timer 输出 TIMEOUT FIRED
- 再次进入check 阶段 输出 2
原因在于setTimeout 0 node 中至少为1ms,也就是取决于机器执行至timer时是否到了可执行的时机。
做个对比就比较清楚了:
setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});
setImmediate(function B(){console.log(4);});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 20);
// 1=>2=>TIMEOUT FIRED
此时间隔时间较长,timer阶段最后才会执行,所以会先执行两次check,出处1,2
下面再看个例子
poll阶段任务队列
var fs = require('fs')
fs.readFile('./yarn.lock', () => {
setImmediate(() => {
console.log('1')
setImmediate(() => {
console.log('2')
})
})
setTimeout(() => {
console.log('TIMEOUT FIRED')
}, 0)
})
// 结果确定:
// 输出始终为1=>TIMEOUT FIRED=>2
- 读取文件,回调进入poll阶段
- 当前无任务队列,直接check 输出1 将setImmediate2加入事件队列
- 接着timer阶段,输出TIMEOUT FIRED
- 再次check阶段,输出2
小结
浏览器的事件循环
浏览器比较清晰一些,就是固定的流程,当前宏任务结束,就是执行所有微任务(不一定是全部,可能基于系统能力,会有所剩下),然后再下一个宏任务,微任务这样交替进行。
node中的事件循环
主要是把握不同阶段和特殊情况的处理,特别是poll阶段和 process.nextTick任务。
结束语
参考文章:
https://zhuanlan.zhihu.com/p/47152694
https://html.spec.whatwg.org/multipage/webappapis.html#event-loop
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
https://hackernoon.com/understanding-js-the-event-loop-959beae3ac40
https://juejin.im/post/5bac87b6f265da0a906f78d8
感谢上述参考文章,关于事件循环这里就总结完毕了,作为自己的一个学习心得。希望能帮助到有需求的同学,一起进步。