JavaScript 執行機制-瀏覽器事件迴圈

legendaryedu發表於2019-04-04

大多數時候,我們去面試,都會遇到,讓你說出一段程式碼的執行結果,或者說出其執行順序。因為javascript是單執行緒的。

關於javascript

javascript 是一門單執行緒語言,最新的H5中提出了webworker,但是單執行緒這個核心沒有發生改變。一切javascript“多執行緒”都是單執行緒模擬出來的,所有新執行緒都受主執行緒的控制,不能獨立執行。意味著,這些執行緒是主執行緒的“子執行緒”,此外,這些執行緒沒有執行IO的許可權,只能為主執行緒分擔一些計算任務。

javascript事件迴圈

javascript是單執行緒的,那麼js任務就需要一個一個的按順序來執行。如果一個任務耗時過長,後面一個任務就需要等待,不過這並不是一件總能讓人接受的事情,如果我們在看一篇文章,文章中部有一張超清圖片,那麼我們的文章要等到圖片載入完之後,才能載入後面的文字,是不是要瘋了~

為了解決這種尷尬,js將任務分成兩類:

  • 同步任務
  • 非同步任務

當我們開啟網站,網頁渲染就是一大推的同步任務,比如 html 骨架,css。 而載入視訊,音樂,圖片等佔資源耗時較長的,就是非同步任務。

javascript 宿主

目前主要應用為兩種,一種是瀏覽器javascript,一種是node,他們的事件迴圈也是不一樣的。

瀏覽器下的js事件迴圈機制

執行棧和事件佇列

javascript程式碼執行時,會將不同變數存在於不同位置:中加以區分。堆中存放物件,棧中存放基礎型別變數和物件指標。

執行棧 和上面所說的棧還不一樣,當我們呼叫一個方法時,js會生成與這個方法相對應的執行環境(context),也叫做“執行上下文”。這個執行環境中,存在這個方法的私有作用域,上層作用域指向,方法引數,在這個作用域中定義的變數還有這個作用域的this物件,而當一系列的方法被呼叫,js還是單執行緒,同一時間只能執行一個方法,於是,這些方法被“排成隊”放在一個地方,就是執行棧

當一個指令碼執行,js引擎解析這段程式碼,同步程式碼按順序加入執行棧,然後開始從頭執行,當前執行一個方法,js向執行棧新增當前方法的執行環境,進入執行環境繼續執行其中程式碼,當這個環境中的程式碼執行完畢返回結果後,js退出這個執行環境,並將這個環境銷燬,回到上一個方法的執行環境,反覆進行這個過程,直到執行棧中全部程式碼執行完畢。

事件佇列 以上說的都是同步程式碼執行,那麼非同步程式碼執行會如何呢?js是非阻塞的,就是靠事件佇列這項機制來實現。

js引擎遇到一個非同步事件,不會傻傻的等待事件返回,而是先將其掛起,然後繼續執行棧中其他任務。當一個非同步事件返回結果後,js將這個事件加入到“事件佇列”,被放入事件佇列的不會立即執行回撥,而是要等待當前棧中所有任務都完成之後,主執行緒閒下來了,然後去看看事件佇列是不是有任務,有的話,就從中取出排在第一位的事件的回撥,放入執行棧,執行其中的同步程式碼,如此反覆,無限迴圈,稱為“事件迴圈”。

macro task 和 micro task

以上我們對事件迴圈是一個巨集觀的表述,實際上,非同步任務也不一樣,執行優先順序也有不同,不同的非同步任務被分為兩類“巨集任務”(macro task)和“微任務”(micro task)

巨集任務

  • setInterval()
  • setTimeout()

微任務

  • new Promise()
  • new mutaionObserver()

在一個時間迴圈中,非同步事件返回結果被放到一個任務佇列,根據非同步事件的型別,會被放到相應的“巨集任務佇列”和“微任務佇列”,當前執行棧為空時,主執行緒會先查微任務是否有事件存在,如果不存在,再去巨集任務佇列取出一個事件,加入當前執行棧;如果存在,依次執行佇列中事件對應的回撥,直到微任務佇列為空,然後再去執行巨集任務佇列事件回撥。

同一次事件迴圈中,微任務永遠領先巨集任務執行。

setTimeout(function () {
    console.log(1);
});

new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
}).then(function(val){
    console.log(val);
})
// 2 3 1
複製程式碼

相關文章