天天看點

JavaScript執行機制 --事件循環

1.關于javascript

javascript是一門 單線程 語言,在最新的HTML5中提出了Web-Worker,但javascript是單線程這一核心仍未改變。是以一切javascript版的"多線程"都是用單線程模拟出來的,一切javascript多線程都是紙老虎!

2.JavaScript 事件循環 Event Loop

javascript 上任務分為兩種,分别為同步任務和異步任務。

同步任務:
在主線程上排隊執行的任務,隻有前一個任務執行完畢,才能執行後一個任務
           
異步任務:
不進入主線程、而進入"任務隊列"(task queue)的任務,隻有"任務隊列"通知主線程,某個異步任務可以執行了,該任務才會進入主線程執行
           

在掘金上盜了一張圖

JavaScript執行機制 --事件循環

** 導圖要表達的内容用文字來表述的話:**

  1. 同步和異步任務分别進入不同的執行"場所",同步的進入主線程,異步的進入Event Table并注冊函數
  2. 當指定的事情完成時,Event Table會将這個函數移入Event Queue。
  3. 主線程内的任務執行完畢為空,會去Event Queue讀取對應的函數,進入主線程執行。
  4. 上述過程會不斷重複,也就是常說的Event Loop(事件循環)。

js引擎存在monitoring process程序,會持續不斷的檢查主線程執行棧是否為空,一旦為空,就會去Event Queue那裡檢查是否有等待被調用的函數。

1_example

$.ajax({
	url:'xxx',
	data:{},
	success: (res)=>{
		console.log('發送成功');
	}
});
console.log('代碼執行結束');
// 代碼執行結束    發送成功
** 上面一段簡易的ajax請求 分析**
 - ajax進入Event Table,注冊回調函數success。
 - 執行console.log('代碼執行結束')。
 - ajax事件完成,回調函數success進入Event Queue。
 - 主線程從Event Queue讀取回調函數success并執行。
           

2_example

我們還經常遇到setTimeout(fn,0)這樣的代碼,0秒後執行又是什麼意思呢?是不是可以立即執行呢?

答案是不會的,setTimeout(fn,0)的含義是,指定某個任務在主線程最早可得的空閑時間執行,意思就是不用再等多少秒了,隻要主線程執行棧内的同步任務全部執行完成,棧為空就馬上執行。舉例說明:

console.log("先執行這裡");
setTimeout(() =>{
 console.log('執行setTimeout');
},0);
console.log('執行結束');
//先執行這裡
//執行結束
//執行setTimeout
           

3_example

上面說完了setTimeout,當然不能錯過它的孿生兄弟setInterval。他倆差不多,隻不過後者是循環的執行。對于執行順序來說,setInterval會每隔指定的時間将注冊的函數置入Event Queue,如果前面的任務耗時太久,那麼同樣需要等待。

唯一需要注意的一點是,對于setInterval(fn,ms)來說,我們已經知道不是每過ms秒會執行一次fn,而是每過ms秒,會有fn進入Event Queue。一旦setInterval的回調函數fn執行時間超過了延遲時間ms,那麼就完全看不出來有時間間隔了。這句話請讀者仔細品味。

let t = +new Date();
setInterval(()=>{
	while((+new Date() -t)<3000){
		//此處模拟睡眠3秒鐘
	}
	console.log('11111');
},1000)
// 控制台會3秒後 立刻輸出2個11111  後續會恢複正常 1秒列印一次11111
           

除了廣義的同步任務和異步任務,我們對任務有更精細的定義:

macro-task(宏任務):

可以了解是每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中擷取一個事件回調并放到執行棧中執行)

包括整體代碼script,setTimeout,setInterval ,setImmediate(Node環境支援)

micro-task(微任務):

可以了解是在目前task執行結束後立即執行的任務

包括 Promise.then,promise.catch ,promise.finally, process.nextTick(Node環境支援)

宏任務與微任務關系圖

JavaScript執行機制 --事件循環

4_example

不同類型的任務會進入對應的Event Queue,比如setTimeout和setInterval會進入相同的Event Queue。

事件循環的順序,決定js代碼的執行順序。進入整體代碼(宏任務)後,開始第一次循環。接着執行所有的微任務。然後再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行所有的微任務

setTimeout(()=>{
	console.log('setTimeout');
},1000);
new Promise(resolve =>{
	console.log("promise");
	resolve();
}).then(function(){
	console.log('then');
});
console.log('console');
// promise
//console
//then
//setTimeout

 - 先遇到setTimeout,那麼将其回調函數注冊後分發到宏任務Event Queue。
 - 接下來遇到了Promise,new Promise立即執行,then函數分發到微任務Event Queue。
 - 遇到console.log(),立即執行。
 - 整體代碼script作為第一個宏任務執行結束,看看有哪些微任務?我們發現了then在微任務Event Queue裡面,執行。
 - 第一輪事件循環結束了,我們開始第二輪循環,當然要從宏任務Event Queue開始。我們發現了宏任務Event
Queue中setTimeout對應的回調函數,立即執行。 結束
           

5_example

console.log(1);
setTimeout(()=>{
  console.log(2);
  new Promise(resolve =>{
  	console.log(3);
  	resolve();
  }).then(()=>{
  	console.log(4);
  });
},0);

new Promise(resolve =>{
  console.log(5);
  resolve();
}).then(()=>{
  console.log(6);
});

setTimeout(()=>{
  console.log(7);
  new Promise(resolve=>{
  	console.log(8);
  	resolve();
  }).then(()=>{
  	console.log(9);
  })
},0);

console.log(10);
// 1 5 10 6 2 3 4 7 8 9
           

第一輪事件循環

1.整體script作為第一個宏任務進入主線程,遇到console.log(1);

2.遇到setTimeout, 将其回調函數分發到宏任務Event Queue中,暫且記為timer1

3.遇到promise ,new Promise直接執行,輸出5。 遇到then 被分發到微任務Event Queue中,記為then1

4.遇到setTimeout, 将其回調函數分發到宏任務Event Queue中,暫且記為timer2

5.遇到console.log(10) 輸出10

關系如圖所示:

宏任務 微任務
timer1 then1
timer2

到這裡 第一輪宏任務執行完成 ,看下微任務清單中then1 一個微任務,執行 輸出6 。第一輪事件執行完成依次輸出 : 1 5 10 6

第二次事件循環

從宏任務清單中timer1 開始:

  1. 遇到console.log(2) 輸出2
  2. 遇到new promise 直接執行,輸出3, 遇到then 分發到微任務Event Queue中,記為then2

    3.第二個宏任務執行完成,檢視是否有微任務,發現有一個then2的微任務 ,輸出 4

    關系圖如下

宏任務 微任務
then2

到這裡 第二輪事件循環執行完成,輸出: 2 3 4

第三次事件循環

從宏任務清單中timer2 開始:

  1. 遇到console.log(7) 輸出7
  2. 遇到new promise 直接執行,輸出8, 遇到then 分發到微任務Event Queue中,記為then3
  3. 第二個宏任務執行完成,檢視是否有微任務,發現有一個then3的微任務 ,輸出9

    關系圖如下

宏任務 微任務
then3

到這裡 第三輪事件循環執行完成,輸出: 7 8 9

最後整體輸出為: 1 5 10 6 2 3 4 7 8 9 (此結果隻是浏覽器環境輸出結果,Node環境輸出略有不同,與執行方式有關)

Nodejs中的事件循環

參照阮一峰大神  [JavaScript 運作機制詳解:再談Event Loop](http://www.ruanyifeng.com/blog/2014/10/event-loop.html)
           

寫在最後

  1. javascript是一門單線程語言
  2. Event Loop是javascript的執行機制

繼續閱讀