本系列最開始是爲了本身面試準備的.後來發現整理愈來愈多,差很少有十二萬字符,最後決定仍是分享出來給你們.javascript
爲了分享整理出來,花費了本身大量的時間,起碼是隻本身用的三倍時間.若是喜歡的話,歡迎收藏,關注我!謝謝!html
前端面試查漏補缺--Index篇(12萬字符合集) 包含目前已寫好的系列其餘十幾篇文章.後續新增值文章不會再在每篇添加連接,強烈建議議點贊,關注合集篇!!!!,謝謝!~前端
後續還會繼續添加設計模式,前端工程化,項目流程,部署,閉環,vue常考知識點 等內容.若是以爲內容不錯的話歡迎收藏,關注我!謝謝!vue
目前本人也在準備跳槽,但願各位大佬和HR小姐姐能夠內推一份靠譜的武漢 前端崗位!郵箱:bupabuku@foxmail.com.謝謝啦!~java
相信你們若是對Event loop有必定了解的話,大概都會知道它的大概步驟是:面試
上面的步驟說得沒錯,很對,可是太淺了.如今的面試應該不會這麼簡單,起碼得加上宏任務,微任務.再整上幾個Promise,async await,讓你判斷.不然都很差意思叫面試題!ajax
爲了不被面試官吊起來打的狀況,咱們如今來詳細地理一理Event Loop.算法
Javascript 有一個 主線程(main thread)和 調用棧(call-stack),全部的代碼都要經過函數,放到調用棧(也被稱爲執行棧)中的任務等待主線程執行。設計模式
JS調用棧採用的是後進先出的規則,當函數執行的時候,會被添加到棧的頂部,當執行棧執行完成後,就會從棧頂移出,直到棧內被清空。前端工程化
MDN的解釋: Web 提供了各類各樣的 API 來完成各類的任務。這些 API 能夠用 JavaScript 來訪問,令你能夠作不少事兒,小到對任意 window 或者 element作小幅調整,大到使用諸如 WebGL 和 Web Audio 的 API 來生成複雜的圖形和音效。
總結: 就是瀏覽器提供一些接口,讓JavaScript能夠調用,這樣就能夠把任務甩給瀏覽器了,這樣就能夠實現異步了!
"任務隊列"是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧一清空,"任務隊列"上第一位的事件就自動進入主線程。可是,若是存在"定時器",主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主線程。
Javascript單線程任務被分爲同步任務和異步任務.
在JavaScript
中,任務被分爲兩種,一種宏任務(MacroTask
)也叫Task
,一種叫微任務(MicroTask
)。
宏任務(MacroTask)
script(總體代碼)
、setTimeout
、setInterval
、setImmediate
(瀏覽器暫時不支持,只有IE10支持,具體可見MDN
)、I/O
、UI Rendering
。微任務(MicroTask)
Process.nextTick(Node獨有)
、Promise
、Object.observe(廢棄)
、MutationObserver
(具體使用方式查看這裏)首先宏觀上是按照這樣的順序執行.也就是前面在"Event loop的初步理解"裏講到的過程
注意:
前面介紹宏任務的時候,提過script也屬於其中.那麼一段代碼塊就是一個宏任務。故全部通常執行代碼塊的時候,先執行的是宏任務script,也就是程序執行進入主線程了,主線程再會根據不一樣的代碼再分微任務和宏任務等待主線程執行完成後,不停地循環執行。
主線程(宏任務) => 微任務 => 宏任務 => 主線程
事件循環的順序是從script開始第一次循環,隨後全局上下文進入函數調用棧,碰到macro-task就將其交給處理它的模塊處理完以後將回調函數放進macro-task的隊列之中,碰到micro-task也是將其回調函數放進micro-task的隊列之中。直到函數調用棧清空只剩全局執行上下文,而後開始執行全部的micro-task。當全部可執行的micro-task執行完畢以後。 接着瀏覽器會執行下必要的渲染 UI,而後循環再次執行macro-task中的一個任務隊列,執行完以後再執行全部的micro-task,就這樣一直循環。
注意: 經過上述的 Event loop 順序可知,若是宏任務中的異步代碼有大量的計算而且須要操做 DOM 的話,爲了更快的 界面響應,咱們能夠把操做 DOM 放入微任務中。
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
複製代碼
這裏須要先理解async/await
。
async/await
在底層轉換成了 promise
和 then
回調函數。 也就是說,這是 promise
的語法糖。
每次咱們使用 await
, 解釋器都建立一個 promise
對象,而後把剩下的 async
函數中的操做放到 then
回調函數中。
async/await
的實現,離不開 Promise
。從字面意思來理解,async
是「異步」的簡寫,而 await
是 async wait
的簡寫能夠認爲是等待異步方法執行完成。
promise1
和promise2
,再執行async1
。async1
再執行promise1
和promise2
。主要緣由是由於在谷歌(金絲雀)73版本中更改了規範,以下圖所示:
RESOLVE(thenable)
和之間的區別Promise.resolve(thenable)
。await
的值被包裹在一個 Promise
中。而後,處理程序附加到這個包裝的 Promise
,以便在 Promise
變爲 fulfilled
後恢復該函數,而且暫停執行異步函數,一旦 promise
變爲 fulfilled
,恢復異步函數的執行。await
引擎必須建立兩個額外的 Promise(即便右側已是一個 Promise
)而且它須要至少三個 microtask
隊列 ticks
(tick
爲系統的相對時間單位,也被稱爲系統的時基,來源於定時器的週期性中斷(輸出脈衝),一次中斷表示一個tick
,也被稱作一個「時鐘滴答」、時標。)。async function f() {
await p
console.log('ok')
}
複製代碼
簡化理解爲:
function f() {
return RESOLVE(p).then(() => {
console.log('ok')
})
}
複製代碼
RESOLVE(p)
對於 p
爲 promise
直接返回 p
的話,那麼 p
的 then
方法就會被立刻調用,其回調就當即進入 job
隊列。RESOLVE(p)
嚴格按照標準,應該是產生一個新的 promise
,儘管該 promise
肯定會 resolve
爲 p
,但這個過程自己是異步的,也就是如今進入 job
隊列的是新 promise
的 resolve
過程,因此該 promise
的 then
不會被當即調用,而要等到當前 job
隊列執行到前述 resolve
過程纔會被調用,而後其回調(也就是繼續 await
以後的語句)才加入 job
隊列,因此時序上就晚了。PromiseResolve
的調用來更改await
的語義,以減小在公共awaitPromise
狀況下的轉換次數。await
的值已是一個 Promise
,那麼這種優化避免了再次建立 Promise
包裝器,在這種狀況下,咱們從最少三個 microtick
到只有一個 microtick
。73如下版本
script start
,調用async1()
時,返回一個Promise
,因此打印出來async2 end
。await
,會新產生一個promise
,但這個過程自己是異步的,因此該await
後面不會當即調用。Promise
和script end
,將then
函數放入微任務隊列中等待執行。null
,而後按照先入先出規則,依次執行。promise1
,此時then
的回調函數返回undefinde
,此時又有then
的鏈式調用,又放入微任務隊列中,再次打印promise2
。await
的位置執行返回的 Promise
的 resolve
函數,這又會把 resolve
丟到微任務隊列中,打印async1 end
。setTimeout
。谷歌(金絲雀73版本)
await
的值已是一個 Promise
,那麼這種優化避免了再次建立 Promise
包裝器,在這種狀況下,咱們從最少三個 microtick
到只有一個 microtick
。await
創造 throwaway Promise
- 在絕大部分時間。promise
指向了同一個 Promise
,因此這個步驟什麼也不須要作。而後引擎繼續像之前同樣,建立 throwaway Promise
,安排 PromiseReactionJob
在 microtask
隊列的下一個 tick
上恢復異步函數,暫停執行該函數,而後返回給調用者。具體詳情查看(這裏)。
Node.js的運行機制以下。
(1)V8引擎解析JavaScript腳本。
(2)解析後的代碼,調用Node API。
(3)libuv庫負責Node API的執行。它將不一樣的任務分配給不一樣的線程,造成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
(4)V8引擎再將結果返回給用戶。
Node 的 Event loop 分爲 6 個階段,它們會按照順序反覆運行
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
複製代碼
Node
的Event loop
一共分爲6個階段,每一個細節具體以下:timers
: 執行setTimeout
和setInterval
中到期的callback
。pending callback
: 上一輪循環中少數的callback
會放在這一階段執行。idle, prepare
: 僅在內部使用。poll
: 最重要的階段,執行pending callback
,在適當的狀況下回阻塞在這個階段。check
: 執行setImmediate
(setImmediate()
是將事件插入到事件隊列尾部,主線程和事件隊列的函數執行完成以後當即執行setImmediate
指定的回調函數)的callback
。close callbacks
: 執行close
事件的callback
,例如socket.on('close'[,fn])
或者http.server.on('close, fn)
。關於Node.js的Event Loop更詳細的過程能夠參考這篇文章
進程是應用程序的執行實例,每個進程都是由私有的虛擬地址空間、代碼、數據和其它系統資源所組成;進程在運行過程當中可以申請建立和使用系統資源(如獨立的內存區域等),這些資源也會隨着進程的終止而被銷燬。
而線程則是進程內的一個獨立執行單元,在不一樣的線程之間是能夠共享進程資源的,因此在多線程的狀況下,須要特別注意對臨界資源的訪問控制。在系統建立進程以後就開始啓動執行進程的主線程,而進程的生命週期和這個主線程的生命週期一致,主線程的退出也就意味着進程的終止和銷燬。主線程是由系統進程所建立的,同時用戶也能夠自主建立其它線程,這一系列的線程都會併發地運行於同一個進程中。
一個進程比如是一個工廠,每一個工廠有它的獨立資源(類比到計算機上就是系統分配的一塊獨立內存),並且每一個工廠之間是相互獨立、沒法進行通訊。
每一個工廠都有若干個工人(一個工人便是一個線程,一個進程由一個或多個線程組成),多個工人能夠協做完成任務(即多個線程在進程中協做完成任務),固然每一個工人能夠共享此工廠的空間和資源(即同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等))。
到此你應該能初步理解了進程和線程之間的關係,這將有助於咱們理解瀏覽器爲何是多進程的,而JavaScript是單線程。
瀏覽器是多進程的(一個窗口就是一個進程),每一個進程包含多個線程.但JavaScript是單線程的.一個主線程(一個stack),多個子線程.
假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準? 因此,爲了不復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特徵,未來也不會改變。
js既然是單線程,那麼確定是排隊執行代碼,那麼怎麼去排這個隊,就是Event Loop。雖然JS是單線程,但瀏覽器不是單線程。瀏覽器中分爲如下幾個線程:
其中JS線程和UI線程相互互斥,也就是說,當UI線程在渲染的時候,JS線程會掛起,等待UI線程完成,再執行JS線程
爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。