JavaScript單線程事件循環(huán)及多線程實(shí)現(xiàn)

一、JavaScript的單線程特點(diǎn)

JavaScript最初設(shè)計(jì)是用來在瀏覽器中操作DOM的,所以JavaScript的單線程特點(diǎn)也是為了避免多線程時帶來的同步問題。

二、JavaScript單線程事件循環(huán)

JavaScript單線程事件循環(huán)特點(diǎn)如下

  • JavaScript是單線程的,這個線程中擁有唯一的一個事件循環(huán)。
  • JavaScript 代碼的執(zhí)行過程中,除了依靠函數(shù)調(diào)用棧來搞定函數(shù)的執(zhí)行順序外,還依靠任務(wù)隊(duì)列(task queue)來搞定另外一些代碼的執(zhí)行。
  • 一個線程中,事件循環(huán)是唯一的,但是任務(wù)隊(duì)列可以擁有多個。
  • 任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)。
    • 同步任務(wù):指在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù);
    • 異步任務(wù):指不進(jìn)入主線程、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程某個異步任務(wù)可以執(zhí)行了,該任務(wù)才會進(jìn)入主線程執(zhí)行。
  • 任務(wù)隊(duì)列又分為macro-task(宏任務(wù))與micro-task(微任務(wù)),它們被分別稱為taskjobs
  • macro-task包括如下六個部分:
    • script(整體代碼),
    • setTimeout,
    • setInterval,
    • setImmediate,
    • I/O,
    • UI rendering。
  • micro-task包括如下四個部分:
    • process.nextTick,
    • Promise,
    • Object.observe(已廢棄),
    • MutationObserver(html5新特性)。
  • setTimeout/Promise等我們稱之為任務(wù)源,而進(jìn)入任務(wù)隊(duì)列的是他們指定的具體執(zhí)行任務(wù)。
  • 來自不同任務(wù)源的任務(wù)會進(jìn)入到不同的任務(wù)隊(duì)列。其中setTimeoutsetInterval同源的。
  • 事件循環(huán)的順序,決定了JavaScript代碼的執(zhí)行順序。它從script(整體代碼)開始第一次循環(huán)
    • 之后全局上下文進(jìn)入函數(shù)調(diào)用棧。直到調(diào)用棧清空(只剩全局),然后執(zhí)行所有的micro-task。當(dāng)所有可執(zhí)行的micro-task執(zhí)行完畢之后。循環(huán)再次從macro-task開始,找到其中一個任務(wù)隊(duì)列執(zhí)行完畢,然后再執(zhí)行所有的micro-task,這樣一直循環(huán)下去。
  • 其中每一個任務(wù)的執(zhí)行,無論是macro-task還是micro-task,都是借助函數(shù)調(diào)用棧來完成。

三、例子

setTimeout(function () {
  console.log('timeout1');
})

new Promise(function (resolve) {
  console.log('promise1');
  for (var i = 0; i < 1000; i++) {
    i == 99 && resolve();
  }
  console.log('promise2');
}).then(function () {
  console.log('then1');
})

console.log('global1');

上面代碼的執(zhí)行順序是什么?

1. 代碼執(zhí)行遇到setTimeout,setTimeout為一個宏任務(wù)源,那么他的作用就是將任務(wù)分發(fā)到它對應(yīng)的隊(duì)列中。
2. script執(zhí)行時遇到Promise實(shí)例。Promise構(gòu)造函數(shù)中的第一個參數(shù),是在new的時候執(zhí)行,因此不會進(jìn)入任何其他的隊(duì)列,而是直接在當(dāng)前任務(wù)直接執(zhí)行了,而后續(xù)的.then則會被分發(fā)到micro-task的Promise隊(duì)列中去。 因此輸出:promise1、promise2。
3. script任務(wù)繼續(xù)往下執(zhí)行,最后只有一句輸出了globa1,然后,全局任務(wù)就執(zhí)行完畢了。
4. 第一個宏任務(wù)script執(zhí)行完畢之后,就開始執(zhí)行所有的可執(zhí)行的微任務(wù)。這個時候,微任務(wù)中,只有Promise隊(duì)列中的一個任務(wù)then1,因此直接執(zhí)行就行了,執(zhí)行結(jié)果輸出then1。
5. 當(dāng)所有的micro-tast執(zhí)行完畢之后,表示第一輪的循環(huán)就結(jié)束了。這個時候就得開始第二輪的循環(huán)。第二輪循環(huán)仍然從宏任務(wù)macro-task開始。這個時候,我們發(fā)現(xiàn)宏任務(wù)中,只有在setTimeout隊(duì)列中還要一個timeout1的任務(wù)等待執(zhí)行。因此就直接執(zhí)行即可。
所以輸出是這樣的:

promise1
promise2
global1
then1
timeout1

四、JavaScript多線程的實(shí)現(xiàn)

test.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>JavaScript Multiple Processes Test</title>
</head>

<body>
  <input type="text" name="haorooms" id="haorooms" value="" />
  <button id="start">start</button>
  <button id="stop">stop</button>
  <button id="ale">alert</button>
  <script type="text/javascript">
    var haorooms = document.getElementById("haorooms"),
        stop = document.getElementById("stop"),
        start = document.getElementById("start"),
        ale = document.getElementById("ale"),
        worker;
    stop.addEventListener("click", function () {
      //用于關(guān)閉worker線程
      worker ? worker.terminate() : {};
    });
    start.addEventListener("click", function () {
      //開起worker線程
      worker = new Worker("test.js");
      worker.onmessage = function () {
        haorooms.value = event.data;
      };
    });
    ale.addEventListener("click", function () {
      alert("i'm a alert worker test");
    });
  </script>
</body>

</html>

test.js

var i = 0;
function mainFunc(){
    i++;
    //把i發(fā)送到瀏覽器的js引擎線程里
    postMessage(i);
}
var id = setInterval(mainFunc,1000);

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容