事件循環Event Loop

1.關於javascript


javascript是一門單線程語言,在最新的HTML5中提出了Web-Worker,但javascript是單線程這一核心仍未改變。因此一切javascript版的"多線程"都是用單線程模擬出來的,一切javascript多線程都是紙老虎!(無論是什麼新框架新語法糖實現的所謂異步,其實都是用同步的方法去模擬的)

2.javascript事件循環

事件循環是js實現異步的一種方法,也是js的執行機制。

首先瀏覽器會把主任務隊列中的同步任務挨個所有執行完,而後再去等待任務隊列中看哪一個任務能夠執行了,
而後把該執行的任務放到主任務隊列中去執行,等這個任務執行完,
再去等待任務中看誰能夠執行了,再把這個任務放到主任務隊列中執行... 如此循環。
這種循環叫作事件循環(Event Loop)

js是單線程,js任務也要一個一個順序執行。若是一個任務耗時過長,那麼後一個任務也必須等着。那麼問題來了,假如咱們想瀏覽新聞,可是新聞包含的超清圖片加載很慢,難道咱們的網頁要一直卡着直到圖片徹底顯示出來?所以聰明的程序員將任務分爲兩類:1)同步任務 2)異步任務javascript

一張圖表示事件循環
image.png
#1.同步和異步任務分別進入不一樣的執行"場所",同步的進入主線程,異步的進入Event Table並註冊函數。
#2.當指定的事情完成時,Event Table會將這個函數移入Event Queue。
#3.主線程內的任務執行完畢爲空,會去Event Queue讀取對應的函數,進入主線程執行。
#4.上述過程會不斷重複,也就是常說的Event Loop(事件循環)。

主線程執行棧什麼時候爲空?
js引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去Event Queue那裏檢查是否有等待被調用的函數。~~~~

因此能夠看作是這樣的:
1.瀏覽器線程先執行同步任務,途中遇到異步任務就將其加入到等待任務隊列中去,而後繼續向下執行,
2.等同步任務所有執行完畢後,再去等待任務隊列中去將全部可執行的微任務逐個執行,
3.執行完微任務後在拿取第一個先到達執行條件的宏任務來執行,
4.執行完後再去等待任務隊列中清理執行完全部已到達執行條件的微任務,
5.而後再拿取下一個宏任務來執行,若是宏任務執行產生微任務或者微任務執行產生宏任務就同樣加入到等待任務隊列中,而後仍是按照主線程每次到等待隊列中先執行完因此的微任務再逐個執行宏任務的順序來走java

異步任務都是誰先到達條件誰先執行,可是誰先到達執行條件也有優先級的問題,這個優先級要看這個任務是宏任務仍是微任務;微任務的優先級比宏任務的要高;程序員

3.運行機制

在事件循環中,每進行一次循環操做稱爲 tick,每一次 tick 的任務處理模型是比較複雜的,但關鍵步驟以下:promise

*     執行一個宏任務(棧中沒有就從事件隊列中獲取)
*     執行過程當中若是遇到微任務,就將它添加到微任務的任務隊列中
*     宏任務執行完畢後,當即執行當前微任務隊列中的全部微任務(依次執行)
*     當前宏任務執行完畢,開始檢查渲染,而後GUI線程接管渲染
*     渲染完畢後,JS線程繼續接管,開始下一個宏任務(從事件隊列中獲取)
一張圖解釋
image.png
宏任務:
(macro)task,能夠理解是每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調並放到執行棧中執行)。
瀏覽器爲了可以使得JS內部(macro)task與DOM任務可以有序的執行,會在一個(macro)task執行結束後,在下一個(macro)task 執行開始前,對頁面進行從新渲染,流程以下:

(macro)task->渲染->(macro)task->...
微任務:
microtask,能夠理解是在當前 task 執行結束後當即執行的任務。也就是說,在當前task任務後,下一個task以前,在渲染以前。
因此它的響應速度相比setTimeout(setTimeout是task)會更快,由於無需等渲染。也就是說,在某一個macrotask執行完後,就會將在它執行期間產生的全部microtask都執行完畢(在渲染前)。
宏任務(macrotask) 微任務(microtask)
誰發起的 宿主(Node、瀏覽器) JS引擎
具體事件 1.script(同步代碼), 2.setTimeout/setInterval, 3.UI rendering/UI事件, 4.postMessage,MessageChannel, 5.setImmediate,I/O(Node.js) 1. Promise, 2.MutaionObserver, 3.Object.observe(已廢棄;Proxy 對象替代), 4.process.nextTick(Node.js)
誰先運行 後運行 先運行
會觸發新一輪tick嗎 不會
代碼塊1
setTimeout(function(){
console.log('1')
});
new  Promise(function(resolve){
  console.log('2');
  resolve();
}).then(function(){
console.log('3')
});
console.log('4');//2  4  3  1

分析:
(1)settimeout是宏任務,雖然先執行的他,可是他被放到了宏任務的eventqueue裏面,(2)往下檢查看有沒有微任務,發現Promise回調函數內的代碼是同步的(微任務)輸出2
(3)then函數把他放入了微任務序列。
(4)主代碼塊(宏任務)輸出4
(5)主線進程全部代碼執行結束。先從微任務queue裏拿回掉函數,輸出3微任務所有完成
(6)再從宏任務的queue拿函數。輸出1瀏覽器

代碼塊2
console.log('1');
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

1.主代碼塊輸出1
2.宏任務1setTimeout(2,3,4,5)
3.微任務1process(6)
4.promise當即執行,回調是同步輸出7,then微任務2(8)
5.宏任務2setTimeout(9,10,11,12)
主代碼所有執行完(餘微任務1,2,宏任務1,2)
6.執行微任務1,輸出6
7.執行微任務2,輸出8
微任務所有執行完(餘宏任務1, 2)
8.執行宏任務1,輸出2,
增微任務process(3)
promise當即執行回調輸出4,微任務(5),
9.執行微任務輸出3,輸出5
10.執行宏任務2,輸出9,
增微任務process(10)
promise當即執行回調輸出11,微任務(12),
11.執行微任務輸出10,輸出12多線程

代碼塊3
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

1.輸出「script start」
2.setTimeout宏任務
3.執行async1()輸出async1 start,執行 async2(),輸出 「async2」。
4.async2執行完畢,將await async2 後面的代碼加入到 微任務隊列async1 end');
五、繼續執行,new Promise, 同步輸出「promise1」。promise.then,加入到微任務隊列,
6.輸出script end
7.當前宏任務執行完畢,查看微任務隊列輸出async1 end 「promise2」
8.微任務所有執行完,檢查宏任務,輸出setTimeout框架

改上面代碼:異步

async function async1() {
    console.log('async1 start');
    let p = await async2();
    console.log(p);
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
    return new Promise(((resolve, reject) => {
        resolve(10);
    }))
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

script start
async1 start
async2
promise1
script end
promise2
10
 async1 end
setTimeout
new promise的操做就跟你 new 一個普通函數沒區別,因此這一句實際上是宏任務,但後面的then是微任務
resolved後的promise對象會在這該級別事件隊列結束以後纔開始執行,及執行與該輪微任務隊列中,始於下一級別宏任務以前
resolved 的 Promise 是在本輪事件循環的末尾執行,老是晚於本輪循環的同步任務。

若是出現兩個這種微任務,則先出現的會先執行async

async 函數中,遇到 await 會跳出當前函數,並讓出線程,再將await後面的代碼放到 微任務(microtask)隊列中
相關文章
相關標籤/搜索