大前端進(jìn)擊之路(二)|JavaScript異步編程
打工人!打工魂!前端才是人上人!此系列總結(jié)于大前端進(jìn)擊之路過(guò)程中的學(xué)習(xí),如果文章中有不對(duì)的地方,希望大家能進(jìn)行批評(píng)改正,互相進(jìn)步。
經(jīng)典面試題
我們先來(lái)看一道經(jīng)典的面試題,讓我們的小腦袋瓜子思考起來(lái)~如果你對(duì)這道題有清晰的思路并且了解背后的原因,那么請(qǐng)直接點(diǎn)贊評(píng)論加關(guān)注!?。。?!


JS采用單線程模式工作的原因
為了回答這個(gè)問題我們首先需要知道JS的執(zhí)行環(huán)境是單線程的,是因?yàn)镴S語(yǔ)言最早是運(yùn)行在瀏覽器端的語(yǔ)言,目的是為了實(shí)現(xiàn)頁(yè)面上的動(dòng)態(tài)交互。實(shí)現(xiàn)動(dòng)態(tài)交互的核心就是DOM操作,因此決定了JS必須是單線程模式工作。我們來(lái)假設(shè)一下如果JS是多線程一起工作的,其中一個(gè)線程修改了一個(gè)DOM元素,另外的一個(gè)線程同時(shí)又要?jiǎng)h除這個(gè)DOM元素,那么此時(shí)瀏覽器就懵逼了,無(wú)法明確以哪個(gè)工作線程為準(zhǔn)。所以為了避免線程同步的問題,JS就被設(shè)計(jì)成了單線程的工作模式。
注意,我們這里說(shuō)的單線程是JS的執(zhí)行環(huán)境是單線程,瀏覽器中是多線程的。
單線程的優(yōu)勢(shì)和弊端
采用單線程的工作模式可以節(jié)省內(nèi)存,節(jié)約上下文切換時(shí)間,沒有鎖的問題。但弊端也很明顯,如果中間有一個(gè)任務(wù)需要花費(fèi)大量的時(shí)間,那么后面的任務(wù)就需要等待這個(gè)任務(wù)完成后才能執(zhí)行,就會(huì)出現(xiàn)假死的情況,對(duì)用戶很不友好。為了解決這個(gè)問題JS給出了兩種執(zhí)行模式:同步模式(Synchronous)和異步模式(Asynchronous)。
同步模式和異步模式
同步模式
同步模式其實(shí)很好理解,舉個(gè)栗子:
我們?nèi)绻凑胀侥J街竺娴脑?,首先先將鍋里裝上水,打開火開始燒水,等待水燒開,再將面、雞蛋、火腿腸等材料拿出,材料準(zhǔn)備好后放入鍋中進(jìn)行煮,煮好后開始干飯。
在這里其實(shí)我們已經(jīng)能夠看出來(lái)問題,我們必須等到水燒開后才去準(zhǔn)備要煮的材料。回到概念里就是在同步模式下我們的代碼是依次執(zhí)行,后一個(gè)任務(wù)必須等待前一個(gè)任務(wù)結(jié)束才能開始執(zhí)行。程序執(zhí)行的順序和代碼編寫的順序是完全一致的。在單線程模式下,大多數(shù)任務(wù)都是以同步模式執(zhí)行。
異步模式
上個(gè)例子中我們?cè)诘却疅_的過(guò)程中什么都沒干,很浪費(fèi)時(shí)間,我們可以在燒水的過(guò)程中將食材都準(zhǔn)備好,等到水燒開后直接放入。
我們?cè)跓倪^(guò)程中去干了別的事情,就屬于異步模式,異步模式中不會(huì)等待異步任務(wù)的結(jié)束才開始執(zhí)行下一個(gè)同步的任務(wù),都是開啟過(guò)后就立即執(zhí)行下一個(gè)任務(wù)。
異步模式對(duì)于JS很重要,沒有異步模式的話我們就無(wú)法同時(shí)處理大量的耗時(shí)任務(wù),就會(huì)給用戶帶來(lái)卡頓和假死的體驗(yàn)。對(duì)于我們開發(fā)者來(lái)說(shuō),會(huì)給我們打開代碼執(zhí)行的順序混亂的問題。
EventLoop事件循環(huán)和消息隊(duì)列
EventLoop是一種循環(huán)機(jī)制,主線程從消息隊(duì)列中讀取任務(wù)并按照順序執(zhí)行,這個(gè)過(guò)程是循環(huán)不間斷的。
消息隊(duì)列是存放異步任務(wù)的地方,當(dāng)我們的同步任務(wù)都執(zhí)行完畢后,EventLoop會(huì)從消息隊(duì)列中依次取出異步任務(wù)放到調(diào)用棧中進(jìn)行執(zhí)行。
宏任務(wù)和微任務(wù)
宏任務(wù)可以理解為每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)
瀏覽器為了讓JS內(nèi)部宏任務(wù)與DOM操作能夠有序的執(zhí)行,會(huì)在一個(gè)宏任務(wù)執(zhí)行結(jié)束后,下一個(gè)宏任務(wù)執(zhí)行開始前,對(duì)頁(yè)面進(jìn)行重新渲染。
宏任務(wù)包括:script整體代碼、setTimeout、setInterval、I/O、UI交互事件、MessageChannel等。
微任務(wù)可以理解為每個(gè)宏任務(wù)執(zhí)行結(jié)束后立即執(zhí)行的任務(wù),發(fā)生在宏任務(wù)后,渲染之前,執(zhí)行微任務(wù)。
所以微任務(wù)的響應(yīng)速度相比宏任務(wù)會(huì)更快,因?yàn)闊o(wú)需等待UI渲染
微任務(wù)包括:Promise.then、MutaionObserver、process.nextTick(Node.js環(huán)境下)等。


圖片取自掘金,侵即刪
異步編程方案的本質(zhì)—回調(diào)函數(shù)
回調(diào)函數(shù):由調(diào)用者定制,交給執(zhí)行者執(zhí)行的函數(shù)。
我們通過(guò) callback 回調(diào)函數(shù)、事件發(fā)布/訂閱、Promise 等來(lái)組織代碼,本質(zhì)都是通過(guò)回調(diào)函數(shù)來(lái)實(shí)現(xiàn)異步代碼的存放與執(zhí)行。

更優(yōu)異步編程統(tǒng)一方案——Promise
Promise概述
Promise概念MDN傳送門
關(guān)于Promise概念性內(nèi)容就不在贅述了,可直接點(diǎn)擊傳送門前往MDN查看。簡(jiǎn)單來(lái)說(shuō)如果我們是用傳統(tǒng)的回調(diào)函數(shù)方式來(lái)完成復(fù)雜的異步流程,就會(huì)無(wú)法避免大量的回調(diào)函數(shù)嵌套,產(chǎn)生回調(diào)地獄的問題。為了避免回調(diào)地獄讓我們開始愉快的Promise的學(xué)習(xí)時(shí)光吧!

Promise案例
我們用Promise來(lái)封裝一個(gè)AJax

Promise的鏈?zhǔn)秸{(diào)用
誤區(qū)
嵌套使用的方式是使用Promise最常見的誤區(qū)。我們要使用promise的鏈?zhǔn)秸{(diào)用的方法盡可能保證異步任務(wù)的扁平化。

鏈?zhǔn)秸{(diào)用的理解
promise對(duì)象then方法,返回了全新的promise對(duì)象??梢栽倮^續(xù)調(diào)用then方法,如果return的不是promise對(duì)象,而是一個(gè)值,那么這個(gè)值會(huì)作為resolve的值傳遞,如果沒有值,默認(rèn)是undefined
后面的then方法就是在為上一個(gè)then返回的Promise注冊(cè)回調(diào)
前面then方法中回調(diào)函數(shù)的返回值會(huì)作為后面then方法回調(diào)的參數(shù)
如果回調(diào)中返回的是Promise,那后面then方法的回調(diào)會(huì)等待它的結(jié)束
Promise的異常處理
then中回調(diào)的onRejected方法
.catch()

了解更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV13a4y1n7tS/
作者:跟兔蟲
鏈接:https://juejin.cn/post/6913476532333707271
來(lái)源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。