[圖片上傳失敗...(image-c3e3cb-1516440392143)]
一练对,js單線程存在的問(wèn)題
js是單線程的耙箍,處理任務(wù)是一件接著一件處理,所以如果一個(gè)任務(wù)需要處理很久的話,后面的任務(wù)就會(huì)被阻塞
所以js通過(guò)Event Loop事件循環(huán)的方式解決了這個(gè)問(wèn)題,在了解事件循環(huán)前扮饶,我們需要了解一些關(guān)鍵詞
二,什么是stack,queue乍构,heap甜无,event loop
- stack(棧):吃多了吐
- queue(隊(duì)列):吃多了...釋放
- heap(堆):存儲(chǔ)obj對(duì)象
執(zhí)行棧
[圖片上傳失敗...(image-252451-1516440392143)]
js引擎運(yùn)行時(shí),當(dāng)代碼開始運(yùn)行的時(shí)候,會(huì)將代碼毫蚓,壓入執(zhí)行棧進(jìn)行執(zhí)行
例:
[圖片上傳失敗...(image-e1a3e3-1516440392143)]
當(dāng)代碼被解析后占键,函數(shù)會(huì)依次被壓入到棧中
[圖片上傳失敗...(image-fd2bff-1516440392143)]
有入棧,就要有出棧元潘,當(dāng)函數(shù)c執(zhí)行完畔乙,開始出棧
[圖片上傳失敗...(image-319c57-1516440392143)]
當(dāng)執(zhí)行棧遇到異步
前面執(zhí)行棧,先入后出翩概,但其實(shí)也是同步的牲距,同步就意味著會(huì)阻塞,所以需要異步钥庇,那當(dāng)執(zhí)行棧中出現(xiàn)異步代碼會(huì)怎么樣
[圖片上傳失敗...(image-49169d-1516440392143)]
此時(shí)在代碼中牍鞠,添加點(diǎn)擊事件和setTimeout,現(xiàn)在觀察一下執(zhí)行順序
[圖片上傳失敗...(image-8ba8f-1516440392143)]
觀察此時(shí)的執(zhí)行棧效果评姨,和上面的函數(shù)嵌套有顯著區(qū)別
1难述,console.log("sync")的語(yǔ)句,不會(huì)被壓入到執(zhí)行棧底部吐句,因?yàn)閏onsole已經(jīng)執(zhí)行結(jié)束了
2胁后,click和settimeout都入棧了,但它們內(nèi)部的console沒有入棧的嗦枢,這說(shuō)明他們沒有執(zhí)行完
3攀芯,如果click沒有執(zhí)行完,那為什么setTimeout會(huì)入棧文虏,不應(yīng)該被阻塞嗎侣诺?
答案是:當(dāng)瀏覽器在執(zhí)行棧執(zhí)行的時(shí)候,發(fā)現(xiàn)有異步任務(wù)之后氧秘,會(huì)交給webapi去維護(hù)年鸳,而執(zhí)行棧則繼續(xù)執(zhí)行后面的任務(wù)
[圖片上傳失敗...(image-12e78f-1516440392143)]
同樣,setTimeout同樣會(huì)被添加到webapi中
[圖片上傳失敗...(image-ef10b2-1516440392143)]
webapi是瀏覽器自己實(shí)現(xiàn)的功能敏储,這里專門維護(hù)事件阻星。
上面setTimeout旁邊有個(gè)進(jìn)度條,這個(gè)進(jìn)度就是設(shè)置的等待時(shí)間
回調(diào)隊(duì)列callback queue
上面的例子已添,當(dāng)setTimeout執(zhí)行結(jié)束的時(shí)候妥箕,是不是就應(yīng)該回到執(zhí)行棧,進(jìn)行執(zhí)行輸出呢更舞?
答案:并不是畦幢!
[圖片上傳失敗...(image-4f8ba-1516440392143)]
此時(shí),倒計(jì)時(shí)結(jié)束后的setTimeout的可執(zhí)行函數(shù)缆蝉,被放如了回調(diào)隊(duì)列
最后宇葱,setTimeout的可執(zhí)行函數(shù)瘦真,被從回調(diào)隊(duì)列中取出,再此放入了執(zhí)行棧
[圖片上傳失敗...(image-47b36b-1516440392143)]
這樣的執(zhí)行過(guò)程就叫 event loop事件循環(huán)
Event Loop的具體流程
執(zhí)行棧任務(wù)清空后黍瞧,才會(huì)從回調(diào)隊(duì)列頭部取出一個(gè)任務(wù)
[圖片上傳失敗...(image-1fb582-1516440392143)]
上面是一個(gè)最簡(jiǎn)單的例子诸尽,輸出結(jié)果是1,3印颤,2
這是為什么您机?
[圖片上傳失敗...(image-d98bb7-1516440392143)]
上圖展示了具體的執(zhí)行順序:
1,console.log(1)被壓入執(zhí)行棧
2年局,setTimeout在執(zhí)行棧被識(shí)別為異步任務(wù)际看,放入webapis中
3,console.log(3)被壓入執(zhí)行棧矢否,此時(shí)setTimeout的可執(zhí)行代碼還在回調(diào)隊(duì)列里等待
4仲闽,console.log(3)執(zhí)行完成后,從回調(diào)隊(duì)列頭部取出console.log(2)僵朗,放入執(zhí)行棧
5赖欣,console.log(2)執(zhí)行
回調(diào)隊(duì)列先進(jìn)先出
需要格外注意,回調(diào)隊(duì)列是先進(jìn)先出的验庙,例:
[圖片上傳失敗...(image-688aee-1516440392143)]
[圖片上傳失敗...(image-b475c7-1516440392143)]
當(dāng)console.log(4)執(zhí)行完成后畏鼓,從回調(diào)隊(duì)列里取出了console.log(2);
注意:只有console.log(2)執(zhí)行完成,執(zhí)行棧再次清空時(shí)壶谒,才會(huì)從回調(diào)隊(duì)列取出console.log(3)
測(cè)試概念是否正確
[圖片上傳失敗...(image-aa146a-1516440392143)]
上面的代碼最后輸出1,5膳沽,2汗菜,4,3挑社,執(zhí)行過(guò)程:
1陨界,輸出1,將2push進(jìn)回調(diào)隊(duì)列
2痛阻,將4push進(jìn)回調(diào)隊(duì)列
3菌瘪,輸出5
4,清空了執(zhí)行棧阱当,讀取輸出2俏扩,發(fā)現(xiàn)有3,將3push進(jìn)回調(diào)隊(duì)列
5弊添,清空了執(zhí)行棧录淡,讀取輸出4
6,清空了執(zhí)行棧油坝,讀取輸出3
至此嫉戚,看起來(lái)好像沒問(wèn)題了刨裆,但是!1蛱础7小!G系邸努潘!,事情還沒有結(jié)束
Macrotask(宏任務(wù))盯桦、Microtask(微任務(wù))
通過(guò)上面的例子慈俯,想必已經(jīng)對(duì)event loop有了一定的了解,現(xiàn)在繼續(xù)看一個(gè)例子
console.log(1);
setTimeout(()=>{
console.log(2)
})
var p = new Promise((resolve,reject)=>{
console.log(3)
resolve("成功")
})
p.then(()=>{
console.log(4)
})
console.log(5)
按照event loop的概念拥峦,應(yīng)該是1贴膘,3,5略号,2刑峡,4,因?yàn)閟etTimeout和then會(huì)被放到回調(diào)隊(duì)列里玄柠,然后又是先進(jìn)先出突梦,所以應(yīng)該是2先輸出,4后輸出
但事實(shí)輸出的順序是1羽利,3宫患,5,4这弧,2娃闲!
[圖片上傳失敗...(image-9fefe9-1516440392143)]
這是因?yàn)閜romise的then方法,被認(rèn)為是在Microtask微任務(wù)隊(duì)列當(dāng)中
什么是Macrotask(宏任務(wù))
Macrotask(宏任務(wù))很好理解匾浪,就是咱們前面介紹過(guò)的回調(diào)隊(duì)列callback queue
什么是Microtask(微任務(wù))
Microtask(微任務(wù))同樣是一個(gè)任務(wù)隊(duì)列皇帮,這個(gè)隊(duì)列的執(zhí)行順序是在清空?qǐng)?zhí)行棧之后
用圖展示就是
[圖片上傳失敗...(image-99e831-1516440392143)]
可以看到Macrotask(宏任務(wù))也就是回調(diào)隊(duì)列上面還有一個(gè)Microtask(微任務(wù))
Microtask(微任務(wù))雖然是隊(duì)列,但并不是一個(gè)一個(gè)放入執(zhí)行棧蛋辈,而是當(dāng)執(zhí)行棧請(qǐng)空属拾,會(huì)執(zhí)行全部Microtask(微任務(wù))隊(duì)列中的任務(wù),最后才是取回調(diào)隊(duì)列的第一個(gè)Macrotask(宏任務(wù))
例:
[圖片上傳失敗...(image-5e26e-1516440392143)]
上面的執(zhí)行過(guò)程是:
1冷溶,將setTimeout給push進(jìn)宏任務(wù)
2渐白,將then(2)push進(jìn)微任務(wù)
3,將then(4)push進(jìn)微任務(wù)
4逞频,任務(wù)隊(duì)列為空礼预,取出微任務(wù)第一個(gè)then(2)壓入執(zhí)行棧
5,輸出2虏劲,將then(3)push進(jìn)微任務(wù)
6托酸,任務(wù)隊(duì)列為空褒颈,取出微任務(wù)第一個(gè)then(4)壓入執(zhí)行棧
7,輸出4
8励堡,任務(wù)隊(duì)列為空谷丸,取出微任務(wù)第一個(gè)then(3)壓入執(zhí)行棧
9,輸出3
10应结,任務(wù)隊(duì)列為空刨疼,微任務(wù)也為空,取出宏任務(wù)中的setTimeout(1)
11鹅龄,輸出1
為什么then是微任務(wù)
這和每個(gè)瀏覽器有關(guān)揩慕,每個(gè)瀏覽器實(shí)現(xiàn)的promise不同,有的then是宏任務(wù)扮休,有的是微任務(wù)迎卤,chrome是微任務(wù),普遍都默認(rèn)為微任務(wù)
除了then以外玷坠,還有幾個(gè)事件也被記為微任務(wù):
- process.nextTick
- promises
- Object.observe
- MutationObserver
console.log("start");
setImmediate(()=>{
console.log(1)
})
Promise.resolve().then(()=>{
console.log(4);
})
Promise.resolve().then(()=>{
console.log(5);
})
process.nextTick(function foo() {
console.log(2);
});
process.nextTick(function foo() {
console.log(3);
});
console.log("end")
上面代碼輸出start,end,2,3,4,5,1
process.nextTick的概念和then不太一樣蜗搔,process.nextTick是加入到執(zhí)行棧底部,所以和其他的表現(xiàn)并不一致
最后的測(cè)試
console.log("1");
setTimeout(()=>{
console.log(2)
Promise.resolve().then(()=>{
console.log(3);
process.nextTick(function foo() {
console.log(4);
});
})
})
Promise.resolve().then(()=>{
console.log(5);
setTimeout(()=>{
console.log(6)
})
Promise.resolve().then(()=>{
console.log(7);
})
})
process.nextTick(function foo() {
console.log(8);
process.nextTick(function foo() {
console.log(9);
});
});
console.log("10")
執(zhí)行順序:
1八堡,輸出1
2樟凄,將setTimeout(2)push進(jìn)宏任務(wù)
3,將then(5)push進(jìn)微任務(wù)
4兄渺,在執(zhí)行棧底部添加nextTick(8)
5缝龄,輸出10
6,執(zhí)行nextTick(8)
7挂谍,輸出8
8二拐,在執(zhí)行棧底部添加nextTick(9)
9,輸出9
10凳兵,執(zhí)行微任務(wù)then(5)
11,輸出5
12企软,將setTimeout(6)push進(jìn)宏任務(wù)
13庐扫,將then(7)push進(jìn)微任務(wù)
14,執(zhí)行微任務(wù)then(7)
15仗哨,輸出7
16形庭,取出setTimeout(2)
17,輸出2
18厌漂,將then(3)push進(jìn)微任務(wù)
19萨醒,執(zhí)行微任務(wù)then(3)
20,輸出3
21苇倡,在執(zhí)行棧底部添加nextTick(4)
22富纸,輸出4
23囤踩,取出setTimeout(6)
24,輸出6
最后結(jié)果是:1晓褪,10堵漱,8,9涣仿,5勤庐,7,2好港,3愉镰,4,6