js的執(zhí)行順序山叮,先同步后異步
異步中任務(wù)隊列的執(zhí)行順序: 先微任務(wù)microtask
隊列著榴,再宏任務(wù)macrotask
隊列
調(diào)用Promise
中的resolve
,reject
屬于微任務(wù)隊列屁倔,setTimeout
屬于宏任務(wù)隊列
注意以上都是 隊列,先進先出暮胧。
微任務(wù)包括 process
.nextTick
锐借,promise
,MutationObserver
往衷。
宏任務(wù)包括 script
钞翔, setTimeout
,setInterval
席舍,setImmediate
布轿,I/O
,UI rendering
。
事件循環(huán)是什么
首先汰扭,JavaScript是一門單線程的語言稠肘,意味著同一時間內(nèi)只能做一件事,但是這并不意味著單線程就是阻塞萝毛,而實現(xiàn)單線程非阻塞的方法就是事件循環(huán)
在JavaScript中项阴,所有的任務(wù)都可以分為
- 同步任務(wù):立即執(zhí)行的任務(wù),同步任務(wù)一般會直接進入到主線程中執(zhí)行
- 異步任務(wù):異步執(zhí)行的任務(wù)笆包,比如ajax網(wǎng)絡(luò)請求环揽,setTimeout定時函數(shù)等
同步任務(wù)與異步任務(wù)的運行流程圖如下:
從上面我們可以看到,同步任務(wù)進入主線程庵佣,即主執(zhí)行棧歉胶,異步任務(wù)進入任務(wù)隊列,主線程內(nèi)的任務(wù)執(zhí)行完畢為空巴粪,會去任務(wù)隊列讀取對應(yīng)的任務(wù)跨扮,推入主線程執(zhí)行。上述過程的不斷重復(fù)就事件循環(huán)
調(diào)用棧(Call Stack)
是一種后進先出的數(shù)據(jù)結(jié)構(gòu)验毡。當(dāng)一個腳本執(zhí)行的時候衡创,js引擎會解析這段代碼,并將其中的同步代碼按照執(zhí)行順序加入調(diào)用棧中晶通,然后從頭開始執(zhí)行璃氢。
事件隊列 (Task Queue)
js引擎遇到一個異步任務(wù)后并不會一直等待其返回結(jié)果,而是會將這個任務(wù)交給瀏覽器的其他模塊進行處理(以webkit為例狮辽,是webcore模塊)一也,繼續(xù)執(zhí)行調(diào)用棧中的其他任務(wù)。當(dāng)一個異步任務(wù)返回結(jié)果后喉脖,js引擎會將這個任務(wù)加入與當(dāng)前調(diào)用棧不同的另一個隊列椰苟,我們稱之為事件隊列。
當(dāng)一個腳本執(zhí)行的時候树叽,js引擎會解析這段代碼舆蝴,并將其中的同步代碼按照執(zhí)行順序加入調(diào)用棧中,然后從頭開始執(zhí)行题诵。
js引擎遇到一個異步事件后并不會一直等待其返回結(jié)果洁仗,而是會將這個事件掛起(其他模塊進行處理),繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)性锭。當(dāng)一個異步事件返回結(jié)果后赠潦,js會將這個事件加入到事件隊列。
被放入事件隊列不會立刻執(zhí)行其回調(diào)草冈,而是等待當(dāng)前執(zhí)行棧中的所有任務(wù)都執(zhí)行完畢她奥, 主線程處于閑置狀態(tài)時瓮增,主線程會去查找事件隊列是否有任務(wù)。如果有哩俭,那么主線程會從中取出排在第一位的事件绷跑,并把這個事件對應(yīng)的回調(diào)放入執(zhí)行棧中,然后執(zhí)行其中的同步代碼…携茂,如此反復(fù)你踩,這樣就形成了一個無限的循環(huán)。這個過程被稱為“事件循環(huán)(Event Loop)”讳苦。
同步任務(wù)
首先带膜,我們用一個棧來表示主線程
當(dāng)有多個同步任務(wù)時,這些同步任務(wù)會依次入棧出棧鸳谜,如下圖
同步任務(wù)1先入棧膝藕,執(zhí)行完之后,出棧咐扭,接著同步任務(wù)2入棧芭挽,依此類推
這只是同步任務(wù)的執(zhí)行方式,那么異步任務(wù)呢蝗肪?
異步任務(wù)
異步任務(wù)會在同步任務(wù)執(zhí)行之后再去執(zhí)行袜爪,那么如果異步任務(wù)代碼在同步任務(wù)代碼之前呢?在js機制里薛闪,存在一個隊列辛馆,叫做任務(wù)隊列,專門用來存放異步任務(wù)豁延。也就是說昙篙,當(dāng)異步任務(wù)出現(xiàn)的時候,會先將異步任務(wù)存放在任務(wù)隊列中诱咏,當(dāng)執(zhí)行完所有的同步任務(wù)之后苔可,再去調(diào)用任務(wù)隊列中的異步任務(wù)
例如下圖,現(xiàn)在存在兩個同步任務(wù)袋狞,兩個異步任務(wù)
js會先將同步任務(wù)1提至主線程焚辅,然后發(fā)現(xiàn)異步任務(wù)1和2,則將異步任務(wù)1和2依次放入任務(wù)隊列
異步任務(wù)分類
js中硕并,又將異步任務(wù)分為宏任務(wù)和微任務(wù)法焰,所以,上述任務(wù)隊列也分為宏任務(wù)隊列和微任務(wù)隊列倔毙,那么,什么是宏任務(wù)乙濒,什么是微任務(wù)呢陕赃?
I/O卵蛉、定時器、事件綁定么库、ajax等都是宏任務(wù)
Promise的then傻丝、catch、finally和process的nextTick都是微任務(wù)
注意:Promise的then等方法是微任務(wù)诉儒,而Promise中的代碼是同步任務(wù)葡缰,并且,nextTick的執(zhí)行順序優(yōu)先于Promise的then等方法忱反,因為nextTick是直接告訴瀏覽器說要盡快執(zhí)行泛释,而不是放入隊列
js中,微任務(wù)總是先于宏任務(wù)執(zhí)行温算,也就是說怜校,這三種任務(wù)的執(zhí)行順序是:同步任務(wù)>微任務(wù)>宏任務(wù)
宏任務(wù)與微任務(wù)
如果將任務(wù)劃分為同步任務(wù)和異步任務(wù)并不是那么的準(zhǔn)確,舉個例子:
console.log(1)
setTimeout(()=>{
console.log(2)}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
如果按照上面流程圖來分析代碼注竿,我們會得到下面的執(zhí)行步驟:
- console.log(1)茄茁,同步任務(wù),主線程中執(zhí)行
- setTimeout() 巩割,異步任務(wù)裙顽,放到 Event Table,0 毫秒后console.log(2)回調(diào)推入 Event Queue 中
- new Promise 宣谈,同步任務(wù)愈犹,主線程直接執(zhí)行
- .then ,異步任務(wù)蒲祈,放到 Event Table
- console.log(3)甘萧,同步任務(wù),主線程執(zhí)行
所以按照分析梆掸,它的結(jié)果應(yīng)該是 1 => 'new Promise' => 3 => 2 => 'then'
但是實際結(jié)果是:1=>'new Promise'=> 3 => 'then' => 2
出現(xiàn)分歧的原因在于異步任務(wù)執(zhí)行順序扬卷,事件隊列其實是一個“先進先出”的數(shù)據(jù)結(jié)構(gòu),排在前面的事件會優(yōu)先被主線程讀取
例子中 setTimeout回調(diào)事件是先進入隊列中的酸钦,按理說應(yīng)該先于 .then 中的執(zhí)行怪得,但是結(jié)果卻偏偏相反
原因在于異步任務(wù)還可以細(xì)分為微任務(wù)與宏任務(wù)
微任務(wù)
一個需要異步執(zhí)行的函數(shù),執(zhí)行時機是在主函數(shù)執(zhí)行結(jié)束之后卑硫、當(dāng)前宏任務(wù)結(jié)束之前
常見的微任務(wù)有:
- Promise.then
- MutaionObserver
- Object.observe(已廢棄徒恋;Proxy 對象替代)
- process.nextTick(Node.js)
宏任務(wù)
宏任務(wù)的時間粒度比較大,執(zhí)行的時間間隔是不能精確控制的欢伏,對一些高實時性的需求就不太符合
常見的宏任務(wù)有:
- script (可以理解為外層同步代碼)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage入挣、MessageChannel
- setImmediate、I/O(Node.js)
這時候硝拧,事件循環(huán)径筏,宏任務(wù)葛假,微任務(wù)的關(guān)系如圖所示
按照這個流程,它的執(zhí)行機制是:
- 執(zhí)行一個宏任務(wù)滋恬,如果遇到微任務(wù)就將它放到微任務(wù)的事件隊列中
- 當(dāng)前宏任務(wù)執(zhí)行完成后聊训,會查看微任務(wù)的事件隊列,然后將里面的所有微任務(wù)依次執(zhí)行完
回到上面的題目
console.log(1)
setTimeout(()=>{
console.log(2)}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
流程如下
遇到 console.log(1) 恢氯,直接打印 1
遇到定時器带斑,屬于新的宏任務(wù),留著后面執(zhí)行
遇到 new Promise勋拟,這個是直接執(zhí)行的勋磕,打印 'new Promise'
.then 屬于微任務(wù),放入微任務(wù)隊列指黎,后面再執(zhí)行
遇到 console.log(3) 直接打印 3
好了本輪宏任務(wù)執(zhí)行完畢朋凉,現(xiàn)在去微任務(wù)列表查看是否有微任務(wù),發(fā)現(xiàn) .then 的回調(diào)醋安,執(zhí)行它杂彭,打印 'then'
當(dāng)一次宏任務(wù)執(zhí)行完,再去執(zhí)行新的宏任務(wù)吓揪,這里就剩一個定時器的宏任務(wù)了亲怠,執(zhí)行它,打印 2
async與await
async 是異步的意思柠辞,await則可以理解為 async wait团秽。所以可以理解async就是用來聲明一個異步方法,而 await是用來等待異步方法執(zhí)行
async和await是es7提供的語法叭首,相比于es6的promise 习勤,具有更高的代碼可讀性
從字面意思理解async是異步的意思,await是等待的意思焙格,那么他們的作用就很容易看出了:
async : 聲明一個函數(shù)是異步的
await : 等待一個異步函數(shù)執(zhí)行完成
語法注意:await必須聲明在async內(nèi)部图毕,因為async會阻斷后邊代碼的執(zhí)行,說到阻斷大家不要慌眷唉,因為這里的阻斷都是在一個async聲明的promise函數(shù)里的阻斷予颤,不會影響整體代碼的執(zhí)行,async外邊的代碼還是照常執(zhí)行
async語法的優(yōu)勢
async語法在處理then鏈的時候有優(yōu)勢冬阳,我們?nèi)绻胮romise的then嵌套可能會寫出多個then鏈蛤虐,雖然解決了回調(diào)地獄的問題,但是感覺好亂啊
用async和await實現(xiàn)多接口調(diào)用的代碼:
function callServe1(){
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve({result:"callserve1"})
}, 1000);
})}
function callServe2(res){
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(res)
}, 1000);
})}
function callServe3(res){
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(res)
}, 1000);
})}
async function getAll(){
const result1 = await callServe1()
const result2 = await callServe2(result1)
const result3 = await callServe3(result2)
console.log(result3) //{ result: 'callserve1' }}
getAll()
async
1.函數(shù)前面加上 async 關(guān)鍵字肝陪,則該函數(shù)會返回一個結(jié)果為 promise 的對象驳庭。
2. async 函數(shù)返回 promise 對象的狀態(tài)。
2.1:如果返回的是一個非 Promise 類型的數(shù)據(jù)氯窍, 則async 函數(shù)返回 promise 的狀態(tài) 為 fulfilled 成功嚷掠。
2.2:如果返回的是一個 Promise對象捏检,則 async 函數(shù)返回 promise 的狀態(tài)由返回的Promise對象的狀態(tài)決定荞驴。
2.3:如果 throw Errow 拋出異常不皆,則 async 函數(shù)返回 promise 的狀態(tài)為 rejected 失敗。
async函數(shù)返回一個promise對象熊楼,下面兩種方法是等效的
function f() {
return Promise.resolve('TEST');
}
async function asyncF() {
return 'TEST';
}
await
1.await 右側(cè)的表達式一般為 promise 對象霹娄。
2.await 是在等一個表達式的結(jié)果,等一個返回值(回調(diào)成功的內(nèi)容)
3.如果 await 右側(cè)是一個 promise 對象鲫骗,則 await 返回的是 promise 成功的值犬耻。
注意:
1.await 必須寫在 async 函數(shù)中,但 async 函數(shù)中可以沒有 await执泰。
2.如果 await 的 promise 失敗了枕磁,就會拋出異常,該異常需要通過 try catch 捕獲處理术吝。
3.如果它等到的是一個 Promise 對象计济,await 就忙起來了,它會阻塞后面的代碼排苍,等著 Promise 對象 resolve沦寂,然后得到 resolve 的值,作為 await 表達式的運算結(jié)果淘衙。
正常情況下传藏,await命令后面是一個 Promise對象,返回該對象的結(jié)果彤守。如果不是 Promise對象毯侦,就直接返回對應(yīng)的值
async function f(){ // 等同于 // return 123 return await 123}f().then(v => console.log(v)) // 123
不管await后面跟著的是什么,await都會阻塞后面的代碼
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
上面的例子中具垫,await 會阻塞下面的代碼(即加入微任務(wù)隊列)侈离,先執(zhí)行 async外面的同步代碼,同步代碼執(zhí)行完做修,再回到 async 函數(shù)中霍狰,再執(zhí)行之前阻塞的代碼
所以上述輸出結(jié)果為:1,fn2饰及,3蔗坯,2
流程分析
通過對上面的了解,我們對JavaScript對各種場景的執(zhí)行順序有了大致的了解
這里直接上代碼:
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')
})
async1()new Promise(function (resolve) {
console.log('promise1')
resolve()}).then(function () {
console.log('promise2')
})
console.log('script end')
分析過程:
- 執(zhí)行整段代碼燎含,遇到 console.log('script start') 直接打印結(jié)果宾濒,輸出 script start
- 遇到定時器了,它是宏任務(wù)屏箍,先放著不執(zhí)行
- 遇到 async1()绘梦,執(zhí)行 async1 函數(shù)橘忱,先打印 async1 start,下面遇到await怎么辦卸奉?先執(zhí)行 async2钝诚,打印 async2,然后阻塞下面代碼(即加入微任務(wù)列表)榄棵,跳出去執(zhí)行同步代碼
- 跳到 new Promise 這里凝颇,直接執(zhí)行,打印 promise1疹鳄,下面遇到 .then()拧略,它是微任務(wù),放到微任務(wù)列表等待執(zhí)行
- 最后一行直接打印 script end瘪弓,現(xiàn)在同步代碼執(zhí)行完了垫蛆,開始執(zhí)行微任務(wù),即 await下面的代碼腺怯,打印 async1 end
- 繼續(xù)執(zhí)行下一個微任務(wù)袱饭,即執(zhí)行 then 的回調(diào),打印 promise2
- 上一個宏任務(wù)所有事都做完了瓢喉,開始下一個宏任務(wù)宁赤,就是定時器,打印 settimeout
所以最后的結(jié)果是:script start栓票、async1 start决左、async2、promise1走贪、script end佛猛、async1 end、promise2坠狡、settimeout
題目如下:
async function async1(){
console.log('1')
await async2()
console.log('2')
}
async function async2(){
console.log('3')
}
console.log('4')
setTimeout(function(){
console.log('5')
},0)
async1();
new Promise(function(resolve){
console.log('6')
resolve();
}).then(function(){
console.log('7')
})
console.log('8')
答案如下:
继找。。逃沿。婴渡。。凯亮。边臼。。