JavaScript - 單執行緒、同步與非同步

JavaScript 的執行方式是單執行緒 (single threaded) 可以理解成它一次只做一件事情。

單執行緒

單執行緒的意思是任務的執行需要排隊。瀏覽器只分配給 JavaScript 一個主執行緒(stack),但一次只能執行一個任務,這些任務形成一個事件佇列 (Queue) 排隊等候執行,但是瀏覽器不只運作 JavaScript 這個程式而已。因為瀏覽器的內核是多線程的,它們藉由瀏覽器內核的控制保持同步,舉例來說有三個常駐線程:

  • JavaScript 引擎
  • GUI 渲染
  • 瀏覽器事件觸發

針對瀏覽器事件觸發,當一個事件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待 JavaScript 引擎的處理。這些事件可來自 JavaScript 引擎當前執行的程式碼,如 setTimeout、也可來自瀏覽器內核如滑鼠點擊、Ajax 非同步請求等,都要排隊處理。

同步執行

同步執行和單執行緒有點類似,同步的意思是,對 JavaScript 來說,一次一個而不是一次兩個或三個。是程式碼中的一行,一次執行一行,而且是照順序的,程式碼會依照出現的順序,一次執行一行。

單執行緒如何執行非同步?

以上可以統整出兩個重點:

  1. 瀏覽器內核除了 JavaScript 的執行緒,還有其他同步執行的執行緒。
  2. setTimeouteventAjax…等無法預期執行時間的操作,都會先被丟到事件佇列排隊,等候執行,等到同步執行的程式碼執行完之後,才會去處理那些佇列中的任務。

單一執行緒表示任務跟任務之間需要排隊,前一個任務完成才會執行下一個任務。可是如果 JavaScript 裡不存在非同步,只能一行一行、由上而下執行,那在同步執行下,如果上一行解析時間很長,那 setTimeoutevent callbackHttp request 這些無法預期觸發執行時間的操作,不就會塞車?對於使用者而言,這樣的使用者體驗很差。

JavaScript 的執行緒會逐一執行 Stack 內的任務,碰上非同步事件,會先執行後面的任務。當這些非同步事件的 callback function 被呼叫時,就會將這些非同步的任務丟到事件列裡面,等 Stack 的任務都完成後,在繼續執行排隊的任務。

單執行緒可以透過事件迴圈 (Event loop) 來實現這些事件,將這些耗時、無法預期執行時間的操作以非同步處理,等執行完同步的程式碼再來處理它們。執行 function 時,會依執行順序把 function 丟到 主執行緒中,等到主執行緒中的任務都執行完畢,才會將事件佇列中待執行的任務拉到主執行緒中執行,執行完畢、清空後,再到 事件佇列中查看是否還有待執行任務,這個查看的過程就稱為 Event Loop

範例

  • 例一

即使 setTimeout()的等待時間為0,但因為會先被JavaScript丟到一邊擱置,等 Stack 的任務完成後,再回來執行 setTimeout()內的 callback function。所以setTimeout()會最晚印出

1
2
3
4
5
6
7
8
console.log("start");
(function () {
console.log("call function");
window.setTimeout(function () {
console.log("setTimeOut");
}, 0);
})();
console.log("end");
  • 例二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
setTimeout(function () {
console.log("5sec");
}, 5000);
setTimeout(function () {
console.log("1sec");
}, 1000);
setTimeout(function () {
console.log("3sec");
}, 3000);

var now = Date.now();
while (true) {
if ((Date, now() > now + 2000)) {
break;
}
}
console.log("Nihao");
setTimeout(function () {
console.log("2sec");
}, 2000);
setTimeout(function () {
console.log("0sec");
}, 0);

這個範例流程可以分為兩條線解析 —

setTimeout:

  1. setTimeout 被先拉到旁邊開始倒數
  2. 倒數完畢,由 callback function 放到事件佇列

while 等待兩秒:

  1. 執行同步執行的程式碼
  2. 印出 Nihao

範例小結

setTimeoutsetInterval 等設定的等待時,例如 3 秒,並不能確保它真會在設定的時間到就馬上執行,只能夠確保它在大於等於 3sec 候才會執行。

因此以上的範例印出的順序及運作流程為:

  1. 5、1、3 秒拉出倒數(倒數完成後由 callback function 丟進事件佇列)
  2. while 等待 2 秒
  3. 印出 Nihao
  4. 2、0 秒拉出倒數(倒數完成後由 callback function 丟進事件佇列)
  5. 主執行緒空了,查看事件佇列
  6. 印出 1、3sec (5sec 還在倒數)
  7. 印出 while 下方的 0sec
  8. 印出 5sec
  9. 印出 2sec

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2020 CYC'S BLOG All Rights Reserved.

UV : | PV :