同步與異步模式
js最初是設(shè)計(jì)使用在瀏覽器上的腳本語言镰踏,由于需要對DOM進(jìn)行操作念链,因此是單線程的執(zhí)行語言。
同步模式
- 非同步執(zhí)行而是排隊(duì)執(zhí)行贝润;
- 變量或函數(shù)的聲明不會產(chǎn)生任何的調(diào)用绊茧;
- js在執(zhí)行引擎當(dāng)中維護(hù)了一個正在工作(執(zhí)行)的工作表,里面記錄當(dāng)前的執(zhí)行任務(wù)打掘,當(dāng)工作表中所有的任務(wù)被清空华畏,這一輪的工作結(jié)束;
- 排隊(duì)執(zhí)行會存在如果遇到耗時多的任務(wù)尊蚁,那么后面的任務(wù)就會被延遲執(zhí)行->阻塞亡笑。
異步模式
// 異步舉例
console.log('global begin')
setTimeout(function timer1() {
console.log('timer1 invoked')
}, 1800)
setTimeout(function timer2() {
console.log('timer2 invoked')
setTimeout(function inner() {
console.log('inner invoked')
}, 1000);
}, 1000);
console.log('global end')
// global begin
// global end
// timer2 invoked
// timer1 invoked
// inner invoked
- 如果沒有異步模式,單線程的js無法同時處理大量的耗時任務(wù)横朋;
- 難點(diǎn):代碼的執(zhí)行順序混亂仑乌;
- 下達(dá)這個任務(wù)開啟的指令然后繼續(xù)往下執(zhí)行,不會等待任務(wù)結(jié)束琴锭。
js實(shí)現(xiàn)異步編程的4種方法:
4種解決方式的根本都是利用了瀏覽器定時器的工作原理晰甚。
回調(diào)函數(shù)
異步編程最基本的方法。
優(yōu)點(diǎn):簡單易理解决帖。
缺點(diǎn):不利于代碼閱讀和維護(hù)厕九,各部分之間高度耦合,流程會很混亂地回,而且每個任務(wù)只能指定一個回調(diào)函數(shù)扁远。事件監(jiān)聽
采用事件驅(qū)動模式腺阳,任務(wù)執(zhí)行不取決于代碼的執(zhí)行順序,而是某個事件是否發(fā)生穿香。
優(yōu)點(diǎn):易理解亭引,可以綁定多個事件,每個事件可以綁定多個回調(diào)函數(shù)皮获,能夠“去耦合”焙蚓,有利于實(shí)現(xiàn)模塊化。
缺點(diǎn):整個程序變成事件驅(qū)動型洒宝,運(yùn)行流程不清晰购公。發(fā)布/訂閱
假設(shè)存在一個“信號中心”,某個任務(wù)執(zhí)行完成雁歌,就向信號中心“發(fā)布(publish)”一個信號宏浩,其他任務(wù)可以向信號中心“訂閱(subscribe)”這個信號,從而知道自己什么時候開始執(zhí)行任務(wù)靠瞎,這就是“發(fā)布/訂閱模式”比庄,也稱“觀察者模式”。
這種方法的性質(zhì)與事件監(jiān)聽類似乏盐,但是可以通過“信號中心”查看佳窑,了解有多少信號、每個信號有多少訂閱者父能,從而監(jiān)控程序的運(yùn)行神凑。
- Promise對象
回調(diào)函數(shù)
由調(diào)用者定義,交給執(zhí)行者執(zhí)行的函數(shù)何吝。
事件循環(huán)與消息隊(duì)列
js引擎線程會維護(hù)一個執(zhí)行棧(調(diào)用棧call stack)溉委,同步代碼會依次加入執(zhí)行棧并執(zhí)行,結(jié)束會退出執(zhí)行棧爱榕。
js引擎線程如果遇到異步(DOM事件監(jiān)聽瓣喊、網(wǎng)絡(luò)請求、setTimeout
計(jì)時器等)呆细,會交給單獨(dú)的線程(??Web APIs)維護(hù)異步任務(wù)型宝,直到滿足一定條件(用戶點(diǎn)擊DOM、網(wǎng)絡(luò)請求成功絮爷、計(jì)時器結(jié)束),由事件觸發(fā)線程將異步對應(yīng)的回調(diào)函數(shù)封裝成任務(wù)并加入消息隊(duì)列梨树。
如果執(zhí)行棧為空坑夯,事件循環(huán)就會啟動,從消息隊(duì)列中取出一個任務(wù)(即異步的回調(diào)函數(shù))放入執(zhí)行棧中執(zhí)行抡四。
- 事件循環(huán)
在線程運(yùn)行過程中柜蜈,接收并執(zhí)行新的任務(wù)仗谆,
- 消息隊(duì)列
消息隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu),可以存放要執(zhí)行的任務(wù)淑履,類似于待辦事件列表隶垮。
異步編程的幾種方式
Promise異步方案、宏任務(wù)/微任務(wù)隊(duì)列
Promise異步方案
Promise對象用于表示一個異步操作的最終完成(或失斆卦搿)及其結(jié)果值狸吞。
Promise有三個狀態(tài):
1.pending([待定]初始狀態(tài))
2.fulfilled([實(shí)現(xiàn)]操作成功)
3.rejected([被否決]操作失敗)
當(dāng)Promise狀態(tài)發(fā)生改變指煎,就會觸發(fā).then()
里的響應(yīng)函數(shù)處理后續(xù)步驟蹋偏,由于.then()
和.catch()
方法返回的是一個新的Promise對象,因此它們可以被鏈?zhǔn)秸{(diào)用至壤。
- Promise基本用法
// Promise 基本實(shí)例
const promise = new Promise((resolve, reject) => {
// 這里用于“兌現(xiàn)”承諾
resolve('100') // 承諾達(dá)成
reject(new Error('promise rejected!')) // 承諾失敗
})
promise.then((value) => {
console.log('resolved', value)
}, (error) => {
console.log('rejected', error)
})
- Promise使用案例
// Promise方式的AJAX
function ajax(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(this.statusText)
}
}
xhr.send()
})
}
Promise常見誤區(qū)與鏈?zhǔn)秸{(diào)用
嵌套使用的方式是使用Promise最常見的錯誤威始,應(yīng)該經(jīng)盡可能保證異步任務(wù)的扁平化。
鏈?zhǔn)秸{(diào)用:
每一個.then()
方法實(shí)際上都是為上一個.then()
返回的Promise對象添加狀態(tài)明確過后的回調(diào)像街。通過鏈?zhǔn)秸{(diào)用避免回調(diào)的嵌套黎棠。Promise異常處理
最好使用catch明確捕獲每一個異常。-
Promise靜態(tài)方法
-
Promise.resolve()
:快速地把一個值轉(zhuǎn)換成Promise對象镰绎;如果包裹一個Promise對象葫掉,那么該P(yáng)romise對象會被原樣返回;還可以傳入一個有then方法的Promise對象跟狱,一般用于將第三方庫的Promise對象轉(zhuǎn)換為原生的Promise對象 -
Promise.reject()
:快速地創(chuàng)建一個失敗的Promise對象
-
-
Promise并行執(zhí)行
同步執(zhí)行多個Promise的方式:-
Promise.all()
:接收一個包含Promise對象的數(shù)組俭厚,將其中的Promise對象看作一個個異步任務(wù),返回一個全新的Promise對象驶臊,等待所有的任務(wù)結(jié)束 -
Promise.race()
:只會等待第一個任務(wù)結(jié)束
-
宏任務(wù)/微任務(wù)隊(duì)列
回調(diào)隊(duì)列中的任務(wù)稱之為「宏任務(wù)」
事件循環(huán)作為任務(wù)驅(qū)動的主線程挪挤,首先執(zhí)行完調(diào)用棧上當(dāng)前的宏任務(wù)(同步任務(wù)),然后再遍歷微任務(wù)隊(duì)列关翎,把微任務(wù)隊(duì)列上所有任務(wù)都執(zhí)行完畢(清空微任務(wù)隊(duì)列)(微任務(wù)也可以往微任務(wù)隊(duì)列中添加微任務(wù))扛门,接著渲染線程,最后從宏任務(wù)隊(duì)列中取一個任務(wù)纵寝,進(jìn)入下一個消息循環(huán)论寨。
宏任務(wù)執(zhí)行過程中可以臨時加上一些額外需求,對于額外需求可以選擇作為一個新的宏任務(wù)進(jìn)到隊(duì)列中排隊(duì)爽茴,也可以作為當(dāng)前任務(wù)的「微任務(wù)」葬凳,直接在當(dāng)前任務(wù)結(jié)束過后立即執(zhí)行,而非到隊(duì)伍末尾重新排隊(duì)室奏。
Promise的回調(diào)會作為微任務(wù)執(zhí)行火焰,setTimeout以宏任務(wù)的形式進(jìn)入隊(duì)列末尾。
微任務(wù)的提出是為了提高整體的響應(yīng)能力胧沫。
目前絕大多是異步調(diào)用都是作為宏任務(wù)執(zhí)行昌简,Promise&MutationObserver占业、process.nextTick會作為微任務(wù)執(zhí)行。
-
產(chǎn)生宏任務(wù)的方式
- script中的代碼塊
- setTimeout()
- setInterval()
- setImmediate()(非標(biāo)準(zhǔn)纯赎、IE和Node.js中支持)
- 注冊事件
-
產(chǎn)生微任務(wù)的方式
- Promise
- MutationObserver
- queueMicrotask()
何時使用微任務(wù)
微任務(wù)執(zhí)行的時機(jī)谦疾,晚于當(dāng)前本輪事件循環(huán)的Call Stack(調(diào)用棧)中的代碼(宏任務(wù)),早于時間處理函數(shù)和定時函數(shù)犬金。
使用微任務(wù)的最主要原因簡單歸納為:
1.減少操作中用戶可感知到的延遲(微任務(wù)中操作dom之后立即渲染)念恍;
2.確保任務(wù)順序的一致性,即便是結(jié)果或數(shù)據(jù)是同步可用的佑附;
3.批量操作的優(yōu)化樊诺。
Generator異步方案、Async/Await語法糖
Generator異步方案
Generator函數(shù)是一個封裝的異步任務(wù)音同,或者說是異步任務(wù)的容器词爬。
- generator由
function *
定義,不同于普通函數(shù)权均,可以暫停執(zhí)行顿膨; - 異步操作需要暫停的地方,用
yield
語句注明叽赊; - 調(diào)用
next()
執(zhí)行g(shù)enerator函數(shù)恋沃,從上次返回的yield
語句處繼續(xù)執(zhí)行。
Async/Await語法糖
語言層面的異步編程標(biāo)準(zhǔn)必指。
async
函數(shù)返回一個Promise對象囊咏,await
等待接收async
函數(shù)的返回值。是Generator的語法糖塔橡。