JavaScript之運行機制,以及Event-Loop的講解

這章節採用先題目後原理的說法爲大家講解。

一、引入題目

我們先看一道題目吧!

console.log(1);
 setTimeout(()=>{
  console.log(2)
},0);
console.log(3)

你們覺得上面打印的順序是什麼呢?如果你不瞭解JS運行機制,那麼你就犯錯吧。其實上面打印的是1 3 2,是不是很奇怪呢?這是爲什麼呢?相信小夥伴現在已經出現了1000多個疑問,不着急,聽我慢慢分解吧。

二、單線程是什麼?爲什麼JS要用單線程?

單線程大白話就是你同一個時間你就只能做一件事情。JavaScript的單線程,與它的用途有關。JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很複雜的同步問題。就比如說,你更新的時候同時點擊刪除,不可能吧。單線程就意味着,所有任務需要排隊,前一個任務結束,纔會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等着

三、理解任務隊列(消息隊列)

JavaScript語言的設計者意識到這個問題,將所有任務分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執行了,該任務纔會進入主線程執行。下面看兩個例子,這樣子比較直觀。

下面這個代碼打印的是什麼:

console.log(1)
while(true){

}
console.log(2)

答案:1。 因爲while是同步任務

再來看看下面打印的是什麼:

console.log(1)
setTimeout(()=>{
  console.log(2)
})
while(true){

}

答案:1,按照單線程原理先執行同步任務,再執行異步任務,while爲同步任務,setTimeout爲異步任務。

四、理解Event Loop

首先我們還是看一個例子:

for (var i = 0; i < 5;i++){
  setTimeout(()=>{
    console.log(i)
  }, 0)
}

你們覺得會打印什麼? 答案是5個5。

這裏要說的是異步任務的放入時間和執行時間(也就是異步執行的運行機制)如下:①②爲備註。

  1. 所有同步任務都在主線程上執行,形成一個執行棧
  2. 當執行到setTimeout異步任務時候,不先放入任務隊列中,而是timer模塊先拿走,
  3. 等setTimeout時間①到了再放入“任務隊列”中,
  4. 等到運行棧裏面的同步任務全部運行完畢後再到“任務隊列”中讀取異步任務②,這個時候setTimeout裏面的函數體就會變成同步任務。
  5. (2,3,4)的反覆循環稱爲Event Loop。只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。

①【當第二個參數缺省時爲時間,默認爲 0;當指定的值小於 4 毫秒,則增加到 4ms(4ms 是 HTML5 標準指定的,對於 2010 年及之前的瀏覽器則是 10ms);也就是說至少需要4毫秒,該setTimeout()拿到任務隊列中。】

②【一旦"執行棧"中的所有同步任務執行完畢(即for循環結束,此時i已經爲5),系統就會讀取已經存放"任務隊列"的setTimeout()(有五個),於是答案是輸出5個5】

像下面的東西都會放在異步隊列中。 

  • setTimeout和setlnterval
  • DOM事件(就是我們平常點擊按鈕的時候會卡頓,就是這種情況)
  • ES6中的Promise
  • Ajax異步請求

 五、Event loop的執行順序

  • 首先 JavaScript 引擎會執行一個宏任務,注意這個宏任務一般是指主幹代碼本身,也就是目前的同步代碼
  • 執行過程中如果遇到微任務,就把它添加到微任務任務隊列中
  • 宏任務執行完成後,立即執行當前微任務隊列中的微任務,直到微任務隊列被清空
  • 微任務執行完成後,開始執行下一個宏任務
  • 如此循環往復,直到宏任務和微任務被清空

宏任務包括 script 同步標籤, setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering

微任務包括 process.nextTick ,promise ,MutationObserver,其中 process.nextTick 爲 Node 獨有。

綜合例子:會打印出什麼?

    setTimeout(()=>console.log('a'))
    Promise.resolve().then(()=>console.log('b')).then(
      ()=>Promise.resolve('c').then(
        (data) => {
          setTimeout(()=>console.log('d'))
          console.log('e')
          return data
        }
      )
    ).then((data)=>{
      console.log(data)
    })
    new Promise((resolve)=>{
      console.log('f')
      resolve('g')
    }).then((res)=>{
      console.log(res)
      }
    );
    console.log('h');

答案:

f, h, b, g, e, c, a, d

解析:b和g是屬於同一層的then,所以,這兩個會被依次輸出的。

題目2:

console.log(1);
let a = setTimeout(() => {console.log(2)}, 0);
console.log(3);
Promise.resolve(4).then(b => {
console.log(b);
clearTimeout(a);
});
console.log(5);

答案:1 3 5 4

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章