本周琢磨一下js運行機制的問題令宿,發(fā)現(xiàn)了很多有意思的東西刽脖,在此拋個磚,如有不對的地方歡迎指正~
1.基礎(chǔ)知識
- js作為瀏覽器腳本語言执隧,它的主要用途是與用戶互動揩抡,以及操作DOM,因此js是單線程镀琉,也避免了同時操作同一個DOM的矛盾問題峦嗤;
- 為了利用多核CPU的計算能力,H5的Web Worker實現(xiàn)的“多線程”實際上指的是“多子線程”屋摔,完全受控于主線程烁设,且不允許操作DOM;
- js引擎存在monitoring process進程钓试,會持續(xù)不斷的檢查主線程執(zhí)行棧是否為空装黑,一旦為空,就會去Event Queue那里檢查是否有等待被調(diào)用的函數(shù)弓熏。這個過程是循環(huán)不斷的恋谭,所以整個的這種運行機制又稱為Event Loop(事件循環(huán))
- 所有同步任務都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)挽鞠;
- 如果在微任務執(zhí)行期間微任務隊列加入了新的微任務疚颊,會將新的微任務加入隊列尾部,之后也會被執(zhí)行信认;
2.js中的異步操作
- setTimeOut
- setInterval
- ajax
- promise
- I/O
3.同步任務 or 異步任務
- 同步任務(synchronous):在主線程上排隊執(zhí)行的任務串稀,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務狮杨;
- 異步任務(asynchronous):不進入主線程母截、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程橄教,某個異步任務可以執(zhí)行了清寇,該任務才會進入主線程執(zhí)行喘漏。
4.宏任務 or 微任務
這里需要注意的是new Promise是會進入到主線程中立刻執(zhí)行,而promise.then則屬于微任務
- 宏任務(macro-task):整體代碼script华烟、setTimeOut翩迈、setInterval
- 微任務(mincro-task):promise.then、promise.nextTick(node)
5.Event Loop事件循環(huán)
- 整體的script(作為第一個宏任務)開始執(zhí)行的時候盔夜,會把所有代碼分為兩部分:“同步任務”负饲、“異步任務”;
- 同步任務會直接進入主線程依次執(zhí)行喂链;
- 異步任務會再分為宏任務和微任務返十;
- 宏任務進入到Event Table中,并在里面注冊回調(diào)函數(shù)椭微,每當指定的事件完成時洞坑,Event Table會將這個函數(shù)移到Event Queue中;
- 微任務也會進入到另一個Event Table中蝇率,并在里面注冊回調(diào)函數(shù)迟杂,每當指定的事件完成時,Event Table會將這個函數(shù)移到Event Queue中本慕;
- 當主線程內(nèi)的任務執(zhí)行完畢排拷,主線程為空時,會檢查微任務的Event Queue锅尘,如果有任務监氢,就全部執(zhí)行,如果沒有就執(zhí)行下一個宏任務鉴象;
- 上述過程會不斷重復,這就是Event Loop事件循環(huán)何鸡;
6.代碼示例
1.第一個例子
console.log(1)
setTimeout(function(){
console.log(2)
},0)
console.log(3)
執(zhí)行結(jié)果:
// 1 3 2
分析:
1.console.log(1)是同步任務纺弊,直接打印1;
2.setTimeout是異步任務骡男,且是宏函數(shù)淆游,放到宏函數(shù)隊列中,等待下次Event Loop才會執(zhí)行隔盛;
3.console.log(3)是同步任務犹菱,直接打印3;
4.主線程執(zhí)行完畢吮炕,沒有微任務腊脱,那么執(zhí)行第二個宏任務setTimeout,打印2龙亲;
5.結(jié)果:1陕凹,3悍抑,2
2.第二個例子
setTimeout(function(){
console.log(1)
});
new Promise(function(resolve){
console.log(2);
for(var i = 0; i < 10000; i++){
i == 9999 && resolve();
}
}).then(function(){
console.log(3)
});
console.log(4);
執(zhí)行結(jié)果:
// 2, 4杜耙, 3搜骡, 1
分析:
1.setTimeout是異步,且是宏函數(shù)佑女,放到宏函數(shù)隊列中记靡;
2.new Promise是同步任務,直接執(zhí)行团驱,打印2摸吠,并執(zhí)行for循環(huán);
3.promise.then是微任務店茶,放到微任務隊列中蜕便;
4.console.log(4)同步任務,直接執(zhí)行贩幻,打印4轿腺;
5.此時主線程任務執(zhí)行完畢,檢查微任務隊列中丛楚,有promise.then族壳,執(zhí)行微任務,打印3趣些;
6.微任務執(zhí)行完畢仿荆,第一次循環(huán)結(jié)束;從宏任務隊列中取出第一個宏任務到主線程執(zhí)行坏平,打印1拢操;
7.結(jié)果:2,4舶替,3令境,1
第三個例子
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
Promise.resolve().then(function() {
console.log(3);
}).then(function() {
console.log('4.我是新增的微任務');
});
console.log(5);
執(zhí)行結(jié)果:
// 1,5顾瞪,3舔庶,4.我是新增的微任務,2
分析:
1.console.log(1)是同步任務陈醒,直接執(zhí)行惕橙,打印1;
2.setTimeout是異步钉跷,且是宏函數(shù)弥鹦,放到宏函數(shù)隊列中;
3.Promise.resolve().then是微任務爷辙,放到微任務隊列中惶凝;
4.console.log(5)是同步任務吼虎,直接執(zhí)行,打印5苍鲜;
5.此時主線程任務執(zhí)行完畢思灰,檢查微任務隊列中,有Promise.resolve().then混滔,執(zhí)行微任務洒疚,打印3;
6.此時發(fā)現(xiàn)第二個.then任務坯屿,屬于微任務油湖,添加到微任務隊列,并執(zhí)行领跛,打印4.我是新增的微任務乏德;
7.這里強調(diào)一下,微任務執(zhí)行過程中吠昭,發(fā)現(xiàn)新的微任務喊括,會把這個新的微任務添加到隊列中,微任務隊列依次執(zhí)行完畢后矢棚,才會執(zhí)行下一個循環(huán)郑什;
8.微任務執(zhí)行完畢,第一次循環(huán)結(jié)束蒲肋;取出宏任務隊列中的第一個宏任務setTimeout到主線程執(zhí)行蘑拯,打印2;
9.結(jié)果:1兜粘,5申窘,3,4.我是新增的微任務孔轴,2
第四個例子
function add(x, y) {
console.log(1)
setTimeout(function() { // timer1
console.log(2)
}, 1000)
}
add();
setTimeout(function() { // timer2
console.log(3)
})
new Promise(function(resolve) {
console.log(4)
setTimeout(function() { // timer3
console.log(5)
}, 100)
for(var i = 0; i < 100; i++) {
i == 99 && resolve()
}
}).then(function() {
setTimeout(function() { // timer4
console.log(6)
}, 0)
console.log(7)
})
console.log(8)
執(zhí)行結(jié)果
//1剃法,4,8距糖,7玄窝,3牵寺,6悍引,5,2
分析:
1.add()是同步任務帽氓,直接執(zhí)行趣斤,打印1;
2.add()里面的setTimeout是異步任務且宏函數(shù)黎休,記做timer1放到宏函數(shù)隊列浓领;
3.add()下面的setTimeout是異步任務且宏函數(shù)玉凯,記做timer2放到宏函數(shù)隊列;
4.new Promise是同步任務联贩,直接執(zhí)行漫仆,打印4;
5.Promise里面的setTimeout是異步任務且宏函數(shù)泪幌,記做timer3放到宏函數(shù)隊列盲厌;
6.Promise里面的for循環(huán),同步任務祸泪,執(zhí)行代碼趋艘;
7.Promise.then是微任務急鳄,放到微任務隊列;
8.console.log(8)是同步任務,直接執(zhí)行揍移,打印8;
9.此時主線程任務執(zhí)行完畢幢妄,檢查微任務隊列中着裹,有Promise.then,執(zhí)行微任務品嚣,發(fā)現(xiàn)有setTimeout是異步任務且宏函數(shù)炕倘,記做timer4放到宏函數(shù)隊列;
10.微任務隊列中的console.log(7)是同步任務翰撑,直接執(zhí)行罩旋,打印7;
11.微任務執(zhí)行完畢眶诈,第一次循環(huán)結(jié)束涨醋;
12.檢查宏任務Event Table,里面有timer1逝撬、timer2浴骂、timer3、timer4宪潮,四個定時器宏任務溯警,按照定時器延遲時間得到可以執(zhí)行的順序,即Event Queue:timer2狡相、timer4梯轻、timer3、timer1尽棕,取出排在第一個的timer2喳挑;
13.取出timer2執(zhí)行,console.log(3)同步任務,直接執(zhí)行伊诵,打印3单绑;
14.沒有微任務,第二次Event Loop結(jié)束曹宴;
15.取出timer4執(zhí)行搂橙,console.log(6)同步任務,直接執(zhí)行笛坦,打印6份氧;
16.沒有微任務,第三次Event Loop結(jié)束弯屈;
17.取出timer3執(zhí)行蜗帜,console.log(5)同步任務,直接執(zhí)行资厉,打印5厅缺;
18.沒有微任務,第四次Event Loop結(jié)束宴偿;
19.取出timer1執(zhí)行湘捎,console.log(2)同步任務,直接執(zhí)行窄刘,打印2窥妇;
20.沒有微任務,也沒有宏任務娩践,第五次Event Loop結(jié)束活翩;
21.結(jié)果:1,4翻伺,8材泄,7,3吨岭,6拉宗,5,2
第五個例子
setTimeout(function() { // timer1
console.log(1);
setTimeout(function() { // timer3
console.log(2);
})
}, 0);
setTimeout(function() { // timer2
console.log(3);
}, 0);
執(zhí)行結(jié)果
//1辣辫,3旦事,2
分析:
1.第一個setTimeout是異步任務且宏函數(shù),記做timer1放到宏函數(shù)隊列急灭;
2.第三個setTimeout是異步任務且宏函數(shù)姐浮,記做timer2放到宏函數(shù)隊列;
3.沒有微任務化戳,第一次Event Loop結(jié)束单料;
4.取出timer1,console.log(1)同步任務点楼,直接執(zhí)行扫尖,打印1;
5.timer1里面的setTimeout是異步任務且宏函數(shù)掠廓,記做timer3放到宏函數(shù)隊列换怖;
6.沒有微任務,第二次Event Loop結(jié)束蟀瞧;
7.取出timer2沉颂,console.log(3)同步任務,直接執(zhí)行悦污,打印3铸屉;
8.沒有微任務,第三次Event Loop結(jié)束切端;
9.取出timer3彻坛,console.log(2)同步任務,直接執(zhí)行踏枣,打印2昌屉;
10.沒有微任務,也沒有宏任務茵瀑,第四次Event Loop結(jié)束间驮;
11.結(jié)果:1,3马昨,2
7.參考文章:
1.ssssyoki《這一次竞帽,徹底弄懂 JavaScript 執(zhí)行機制》
https://juejin.im/post/59e85eebf265da430d571f89#heading-9
2.阮一峰《JavaScript 運行機制詳解:再談Event Loop》
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
3.ziwei3749 《深入理解JS引擎的執(zhí)行機制》
https://segmentfault.com/a/1190000012806637
4.Jake《Tasks, microtasks, queues and schedules》
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly