JavaScript是一門單線程語言,原由是設計之初js只用來操做dom,對錶單進行簡單的校驗。在這種執行環境簡單的狀況下,天然就選擇了單線程來處理程序。可是單線程若是遇到執行時間較長的程序片斷,會拖延甚至阻塞程序的執行,對於用戶來講,頁面呈現"卡死狀態",這是最糟糕的體驗。node
爲了解決上述問題,JavaScript將程序的執行分爲同步和異步。面試
在JavaScript中寫異步代碼也叫作異步編程,進行異步編程的方式有:編程
回調函數promise
事件監聽瀏覽器
發佈訂閱markdown
promise多線程
❝異步即未來, 異步任務就是未來執行的任務dom
❞
js引擎是單線程的,那麼異步任務是如何維護的呢?異步
js引擎負責解析並編譯js代碼。制定做用域標準,分配內存,建立執行上下文調用棧...。編譯好的代碼放到運行環境中去運行,而運行環境會維護異步任務。async
對瀏覽器而言,瀏覽器是多線程的,它能夠分配線程去倒計時定時器,發送請求,事件監聽等。當定時器中的事件倒計時完畢,將其扔到也是由瀏覽器維護的消息隊列中,當遇到其餘異步時,瀏覽器會分配進程去處理,處理完畢也是扔到消息隊列中。等待js引擎去執行。
以上所說的異步任務均是宏任務。
關於異步還有一些有趣的事情。
「有趣的異步控制檯」
console也並不是js標準,沒有具體的約束和規則去指定console的行爲,console的行爲是由運行環境決定的。
摘自你不知道的JavaScript(中卷) p141
❝不一樣的瀏覽器和 JavaScript 環境能夠按照本身的意願來實現,有時候這會引發混淆。 尤爲要提出的是,在某些條件下,某些瀏覽器的 console.log(..) 並不會把傳入的內容立 即輸出。出現這種狀況的主要緣由是,在許多程序(不僅是 JavaScript)中,I/O 是很是低 速的阻塞部分。因此,(從頁面 /UI 的角度來講)瀏覽器在後臺異步處理控制檯 I/O 可以提 高性能,這時用戶甚至可能根本意識不到其發生。
❞
console一般是進行程序調試寫的比較多,若是產生了一些迷惑性行爲,能夠這樣作
❝最好的選擇是在 JavaScript 調試器中使用斷點, 而不要依賴控制檯輸出。次優的方案是把對象序列化到一個字符串中,以強制執行一次「快照」,好比經過 JSON.stringify(..)。
❞
當檢測到js主線程中的調用棧爲空時(主線程會維護一個巨大的匿名函數,這個匿名函數用來執行js代碼)。 瀏覽器早已提供好了用做事件觸發的線程,事件觸發線程從消息隊列中按照隊列排序取出一個任務放到執行棧中壓棧執行。
執行過程當中遇到的問題:
若是遇到宏任務: 將其給瀏覽器進行處理,處理完畢放入消息隊列中排隊。
若是遇到微任務:將其放到微任務隊列中,依次執行,不用去排隊。也就是微任務是能夠"插隊"的。等到微任務隊列中的全部微任務所有執行完畢。纔會開啓下一輪事件循環。
宏任務有: script
、setTimeout
、setInterval
、setImmediate
(瀏覽器暫時不支持,只有IE10支持)、I/O
、UI Rendering
。
微任務有: Process.nextTick(node)
、Promise
、MutationObserver
模擬執行一個宏任務
setTimeout(() => {
console.log('setTimeout');
}, 0);
複製代碼
模擬執行一個微任務
queueMicrotask(() => {
console.log('queueMicrotask');
});
複製代碼
上面兩種方式不會建立額外的對象,不會形成浪費(好比經過promise
建立微任務.)
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')
複製代碼
js編譯完成,進入瀏覽器執行環境開始執行。
遇到async1,async2函數分配內存
打印script start
遇到setTimeout,計時完成放到消息隊列中,等待主線程空閒時執行。
調用async1函數,打印async1 start
遇到awiait,同步代碼,執行後面的async2函數,打印async2
(async await是generator的語法糖。能夠用generator來實現async await的效果。generator也是微任務隊列) 將後面的片斷放到微任務隊列中。
建立Promise實例對象,打印promise1
, 將then函數扔到微任務隊列中。
打印script end
主線程空閒, 事件觸發線程拿到微任務隊列中的第一個任務,放到主線程中的調用棧中執行,打印async1 end
而後執行微任務隊列中的第二個微任務,打印promise2
,微任務隊列清空,去宏任務隊列中拿到第一個任務,放到主線程中執行,打印 setTimeout
❝在面試或者在寫代碼中,只要明白了這套規則,就瞭解了js的執行機制。對於一些執行機制以及執行順序的問題也就迎刃而解了。
❞