JS事件循環Event Loop

上週寫了篇關於setTimeout的文章,其實也就牽扯到了js的運行機制。因此,這周就來談談javascript的運行機制吧。

那就先問個問題吧😁。javascript

爲何JavaScript是單線程的?

仍是我來回答吧:

單線程意思就是說同一個時間只能作一件事。那這樣的話效率不是很低?也沒有啦,其實javascript的單線程特色是跟他的用途有關的。做爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操做DOM。假如不是單線程的話,在一個線程當咱們在給某個DOM節點增長內容的時候,另外一個線程正在刪除這個DOM節點的內容,那還得了,那不是亂套了嗎。因此javascript只能是單線程。html

雖然javascript是單線程,可是javascript中有同步和異步的概念,解決了js阻塞的問題。

同步和異步:

一.同步:

若是在一個函數返回的時候,調用者就可以獲得預期結果(即拿到了預期的返回值或者看到了預期的效果),那麼這個函數就是同步的。java

用代碼解釋一下:node

console.log('Hello');
複製代碼

若是在函數返回時,就看到了預期的效果:在控制檯打印了Helloajax

二.異步:

若是在函數返回的時候,調用者還不可以獲得預期結果,而是須要在未來經過必定的手段獲得,那麼這個函數就是異步的。promise

代碼解釋:瀏覽器

fs.readFile('test.txt', 'utf8', function(err, data) {
    console.log(data);
});
複製代碼

在上面的代碼中,咱們但願經過fs.readFile函數讀取文件foo.txt中的內容,並打印出來。可是在fs.readFile函數返回時,咱們指望的結果並不會發生,而是要等到文件所有讀取完成以後。若是文件很大的話可能要很長時間。異步

小總結:

同步方法調用一旦開始,調用者必須等到方法調用返回後,才能繼續後續的行爲。svg

異步方法調用更像一個消息傳遞,一旦開始,方法調用就會當即返回,調用者就能夠繼續後續的操做。而,異步方法一般會在另一個線程中,「真實」地執行着。整個過程,不會阻礙調用者的工做。函數

javascript就能夠進行同步任務和異步任務。把讀文件這種操做,ajax請求這些須要耗時的任務放到任務隊列中,我仍是可以一步步的繼續下面的任務。因此啊,javascript仍是能夠很6。那麼異步任務裏面只是放要進行異步操做的任務嗎,裏面會發生啥呢?

任務隊列:

上面說過了javascript裏面的任務有兩種,同步任務和異步任務。

同步任務是指:在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務。

異步任務指的是,不進入主線程、而進入"任務隊列"的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。

先看個小栗子吧:

console.log("a");
setTimeout(function () {
    console.log("b");
},0);
console.log("c");

//a
//c
//b
複製代碼

js中代碼從上往下執行,執行第一行代碼的時候控制檯輸出a,執行到第二行代碼的時候遇到了setTimeout函數,由於setTimeout函數是個異步函數,因此,瀏覽器會記住這個事件,添加到時間表中,以後把這個事件的回調函數入棧到任務隊列中。而此時主線程程序繼續往下運行,到了第五行:console.log("c"),執行這條,控制檯輸出c。這時候主線程空了,他會到任務隊列裏面去查找是否有能夠執行的任務,有的話直接拿出來執行,沒有的話會一直去詢問,等到有能夠執行的。

爲了更好的說明任務隊列和事件循環,先看一張圖。😄

![](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1280" height="970"></svg>) 這張圖片裏面已經畫出了js的事件循環的流程了。 流程:

  1. 全部同步任務都在主線程上執行,造成一個執行棧。
  2. 當主線程中的執行棧爲空時,檢查事件隊列是否爲空,若是爲空,則繼續檢查;如不爲空,則執行3;
  3. 取出任務隊列的首部,壓入執行棧;
  4. 執行任務;
  5. 檢查執行棧,若是執行棧爲空,則跳回第 2 步;如不爲空,則繼續檢查;

Event Loop:

事件循環其實就是入棧出棧的循環。上面例子中說到了setTimeout,那setInterval呢,Promise呢等等等等,有不少異步的函數。可是這些異步任務有分宏任務(macro-task)和微任務(micro-task):

macro-task包括: setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。

每一次Event Loop觸發時:

  1. 執行完主執行線程中的任務。
  2. 取出micro-task中任務執行直到清空。
  3. 取出macro-task中一個任務執行。
  4. 取出micro-task中任務執行直到清空。
  5. 重複3和4。
其實promise的then和catch纔是microtask,自己的內部代碼不是。

注意:

在瀏覽器瀏覽器和node中的執行不同。

任務隊列裏面是「先入先出」的。

那來個小栗子測試一下你是否是已經徹底理解啦:
console.log('global')

for (var i = 1;i <= 5;i ++) {
  setTimeout(function() {
    console.log(i)
  },i*1000)
  console.log(i)
}

new Promise(function (resolve) {
  console.log('promise1')
  resolve()
 }).then(function () {
  console.log('then1')
})

setTimeout(function () {
  console.log('timeout2')
  new Promise(function (resolve) {
    console.log('timeout2_promise')
    resolve()
  }).then(function () {
    console.log('timeout2_then')
  })
}, 1000)
複製代碼

控制檯輸出: ![](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="668" height="273"></svg>) 這個博客可以很是好的加深理解:深刻理解 JavaScript 事件循環(一)— event loop

總結:

這裏主要是講了在瀏覽器端js事件循環。這篇文章能夠幫助更好的理解node和瀏覽器環境下不一樣的事件循環:瀏覽器和Node不一樣的事件循環(Event Loop)

相關文章
相關標籤/搜索