淺談JavaScript中的事件循環機制

事件循環(Event Loop)

背景

JavaScript是一門單線程非阻塞的腳本語言,單線程意味着,JavaScript代碼在執行的任什麼時候候,都只有一個主線程來處理全部的任務。而非阻塞則是當代碼須要進行一項異步任務(沒法馬上返回結果,須要花必定時間才能返回的任務,如I/O事件)的時候,主線程會掛起(pending)這個任務,而後在異步任務返回結果的時候再根據必定規則去執行相應的回調。
在乎識到該問題之際,html5新特性中的web worker可讓JavaScript成爲一門多線程語言,但實際開發中使用web worker存在着諸多限制。javascript

瀏覽器的Event Loop

js引擎的過程

同步編程案例

思考一下這段代碼的結果?
clipboard.pnghtml

顯然,同步函數是由上至下的執行順序
clipboard.pnghtml5

異步編程案例

clipboard.png

由於setTimeout是異步函數,js執行機制是先將同步函數執行完畢,再執行異步函數
clipboard.pngjava

執行棧與事件隊列

當javascript代碼執行的時候會將不一樣的變量存於內存中的不一樣位置:堆(heap)和棧(stack)中來加以區分。其中,堆裏存放着一些對象。而棧中則存放着一些基礎類型變量以及對象的指針。以下:
clipboard.png
當咱們調用一個方法的時候,js會生成一個與這個方法對應的執行環境(context),又叫執行上下文。這個執行環境中存在着這個方法的私有做用域,上層做用域的指向,方法的參數,這個做用域中定義的變量以及這個做用域的this對象。 而當一系列方法被依次調用的時候,由於js是單線程的,同一時間只能執行一個方法,因而這些方法被排隊在一個單獨的地方。這個地方被稱爲執行棧。
當腳本第一次執行的時候,js引擎會解析這段代碼,並將其中的同步代碼按照執行順序加入執行棧中,而後從頭開始執行。若是當前執行的是一個方法,那麼js會向執行棧中添加這個方法的執行環境,而後進入這個執行環境繼續執行其中的代碼。當這個執行環境中的代碼 執行完畢並返回結果後,js會退出這個執行環境並把這個執行環境銷燬,回到上一個方法的執行環境。這個過程反覆進行,直到執行棧中的代碼所有執行完畢。web

宏任務(macro taks)與微任務(micro task)

在異步函數中,能夠細分爲兩種任務,宏任務與微任務。
宏任務有如下幾種:
①I/O
②setTimeout
③setInterval
④setImmediate
⑤requestAnimationFrame編程

微任務有如下幾種:
①process.nextTick
②MutationObserver
③Promise.then catch finallypromise

異步編程案例

clipboard.png

當異步函數中同時存在微任務和宏任務的時候,先執行完微任務,再執行宏任務
clipboard.png瀏覽器

總結

瀏覽器中的事件循環機制就是js在執行代碼時,由上至下遍歷,優先執行同步函數,在遇到異步函數的時候,將該任務放置執行棧;當任務隊列中沒有同步函數以後便開始執行執行棧中的異步函數,優先執行微任務,後執行宏任務。
js執行順序: 同步函數 -> 微任務 -> 宏任務多線程

思考題

setTimeout

console.log('script start')    
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
    
// 輸出順序:script start->script end->settimeout

這個很簡單,setTimeout中的語句會進入宏任務,後置執行。異步

promise

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')

// 輸出順序: script start->promise1->promise1 end->script end->promise2->settimeout

兩個要點:
①promise中的then函數中代碼會進入微任務
②promise中的resolve只是更改promise的狀態,所以後面的語句會繼續執行。

async/await

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}

console.log('script start');
async1();
console.log('script end')

// 輸出順序:script start->async1 start->async2->script end->async1 end

async函數表示函數裏面可能會有異步方法,await後面跟一個表達式,async方法執行時,遇到await會當即執行表達式,而後把表達式後面的代碼放到微任務隊列裏,讓出執行棧讓同步代碼先執行

綜合測試

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');

這題不公佈答案,自行答題。

相關文章
相關標籤/搜索