徹底弄懂JavaScript 執行機制
很多人在面試的時候都會碰到這麼一道面試題:給一段程式碼,寫出執行結果和順序。其中側重的知識點可能也不盡相同。寫這篇文章,主要是把其中可能涉及到的知識點都簡單說一下,自己也好好梳理一下。如果文章有說的不對的地方,儘管diss。
瀏覽器核心分為渲染引擎和JS引擎,不過由於JS引擎越來越獨立,核心就特指渲染引擎了。常見瀏覽器核心:IE-Trident引擎、Firefox-gecko引擎、Chrome/safari-webkit引擎、opera-Presto引擎後棄用使用webkit引擎、
- 渲染引擎:負責對網頁語法的解釋並渲染網頁。渲染引擎決定了瀏覽器如何顯示網頁的內容以及頁面的格式資訊。不同的瀏覽器核心對網頁編寫語法的解釋也有不同,因此同一網頁在不同的核心的瀏覽器裡的渲染(顯示)效果也可能不同 。
- JS引擎:解釋並編譯程式碼,也就是執行JS程式碼的。單執行緒(如果是多執行緒,那對於同一個dom一個程序刪除一個程序編輯,瀏覽器會很尷尬,不知道怎麼辦),那麼問題來了,既然是單執行緒那如果遇到耗時很長的任務(載入超高清圖片)那後面的任務豈不是會很生氣,偉大的程式設計師當然有辦法解決這個問題了,那就是把任務分為同步任務和非同步任務,頁面元素渲染這種就放到同步任務裡面,資料請求,圖片載入這種耗時長的就放進非同步任務。盜圖一張
有點抽象,來段程式碼解釋一下
let data = []; $.ajax({ url:www.javascript.com, data:data, success:() => { console.log('傳送成功!'); } }) console.log('程式碼執行結束');
- ajax進入Event Table,註冊回撥函式success
- 執行console.log('程式碼執行結束')
- ajax事件完成,回撥函式進入Event Queue
- 主執行緒從Event Queue讀取回調函式並執行
上述過程不斷重複,就是所謂的Event Loop(事件迴圈)
那如果用過promise的同學要問了,promise和setTimeout都是非同步任務,執行順序是怎樣的呢。任務當然還有更精細的劃分,不然提出這個問題解決不了,豈不是很尷尬。
- macro-task(巨集任務):包括整體程式碼script,setTimeout,setInterval
- micro-task(微任務)
js程式碼執行的順序(也是時間迴圈的順序),相同型別任務會進入相同的Event Queue,進入整體程式碼(巨集任務)後,開始第一次迴圈。接著執行所有的微任務。然後再次從巨集任務開始,找到其中一個任務佇列執行完畢,再執行所有的微任務。好吧,有點抽象,舉個栗子
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
- 這段程式碼作為巨集任務,進入主執行緒。
- 先遇到
setTimeout
,那麼將其回撥函式註冊後分發到巨集任務Event Queue。 - 接下來遇到了
Promise
,new Promise
立即執行,then
函式分發到微任務Event Queue。 - 遇到
console.log()
,立即執行。 - 這時整體程式碼script作為第一個巨集任務執行結束,看看有哪些微任務?我們發現了
then
在微任務Event Queue裡面,執行。 - ok,第一輪事件迴圈結束了,我們開始第二輪迴圈,當然要從巨集任務Event Queue開始。我們發現了巨集任務Event Queue中
setTimeout
對應的回撥函式,立即執行。 - 結束。
這裡再說一下setTimeout,都知道這是用來進行非同步延遲任務的,但並不意味著程式碼是在設定的時間後執行,而是指經過設定的時間把要執行的任務加入到Event Queue中,又因為是單執行緒任務要一個一個執行,如果當前佇列前面的任務需要的時間太久,那麼只能等著,導致實際時間超過設定的時間。
setTimeout(fn,0)
的含義是,指定某個任務在主執行緒最早可得的空閒時間執行,意思就是不用再等多少秒了,只要主執行緒執行棧內的同步任務全部執行完成,棧為空就馬上執行。要補充的是,即便主執行緒為空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒。
對於setInterval(fn,ms)
來說,我們已經知道不是每過ms
秒會執行一次fn
,而是每過ms
秒,會有fn
進入Event Queue。一旦setInterval
的回撥函式fn
執行時間超過了延遲時間ms
,那麼就完全看不出來有時間間隔了。