原文
大綱
1斗这、場景分析
2、執(zhí)行機(jī)制相關(guān)知識點(diǎn)
3虏束、以實(shí)例來說明JavaScript的執(zhí)行機(jī)制
4棉饶、相關(guān)概念
1、場景分析
/*
以下這段代碼的執(zhí)行結(jié)果是什么镇匀?
如果依照:js是按照語句出現(xiàn)的順序執(zhí)行這個(gè)理念照藻,
那么代碼執(zhí)行的結(jié)果應(yīng)該是:
//"定時(shí)器開始啦"
//"馬上執(zhí)行for循環(huán)啦"
//"執(zhí)行then函數(shù)啦"
//"代碼執(zhí)行結(jié)束"
但結(jié)果并不是這樣的,得到的結(jié)果是:
//"馬上執(zhí)行for循環(huán)啦"
//"代碼執(zhí)行結(jié)束"
//"執(zhí)行then函數(shù)啦"
//"定時(shí)器開始啦"
*/
setTimeout(function(){
console.log('定時(shí)器開始啦')
});
new Promise(function(resolve){
console.log('馬上執(zhí)行for循環(huán)啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('執(zhí)行then函數(shù)啦')
});
console.log('代碼執(zhí)行結(jié)束');
2汗侵、執(zhí)行機(jī)制相關(guān)知識點(diǎn)
2.1幸缕、關(guān)于javascript
javascript是一門單線程語言,在最新的HTML5中提出了Web-Worker晰韵,但javascript是單線程這一核心仍未改變发乔。所以一切javascript版的"多線程"都是用單線程模擬出來的。
2.2雪猪、javascript的同步和異步
單線程就意味著栏尚,所有任務(wù)需要排隊(duì),前一個(gè)任務(wù)結(jié)束只恨,才會(huì)執(zhí)行后一個(gè)任務(wù)译仗。如果前一個(gè)任務(wù)耗時(shí)很長,后一個(gè)任務(wù)就不得不一直等著官觅。
如果排隊(duì)是因?yàn)橛?jì)算量大纵菌,CPU忙不過來,倒也算了休涤,但是很多時(shí)候CPU是閑著的咱圆,因?yàn)镮O設(shè)備(輸入輸出設(shè)備)很慢(比如Ajax操作從網(wǎng)絡(luò)讀取數(shù)據(jù)),不得不等著結(jié)果出來,再往下執(zhí)行闷堡。
JavaScript語言的設(shè)計(jì)者意識到,這時(shí)主線程完全可以不管IO設(shè)備疑故,掛起處于等待中的任務(wù)杠览,先運(yùn)行排在后面的任務(wù)。等到IO設(shè)備返回了結(jié)果纵势,再回過頭踱阿,把掛起的任務(wù)繼續(xù)執(zhí)行下去。
于是钦铁,所有任務(wù)可以分成兩種软舌,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)牛曹。同步任務(wù)指的是佛点,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢黎比,才能執(zhí)行后一個(gè)任務(wù)超营;異步任務(wù)指的是,不進(jìn)入主線程阅虫、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù)演闭,只有"任務(wù)隊(duì)列"通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了颓帝,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行米碰。
1购城、同步和異步任務(wù)分別進(jìn)入不同的執(zhí)行"場所"吕座,同步的進(jìn)入主線程,異步的進(jìn)入Event Table并注冊函數(shù)工猜。
2米诉、當(dāng)Event Table中指定的事情完成時(shí),會(huì)將這個(gè)函數(shù)移入Event Queue篷帅。
3史侣、主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會(huì)去Event Queue讀取對應(yīng)的函數(shù)魏身,進(jìn)入主線程執(zhí)行惊橱。
4、上述過程會(huì)不斷重復(fù)箭昵,也就是常說的Event Loop(事件循環(huán))税朴。
5、我們不禁要問了,那怎么知道主線程執(zhí)行棧為空罢帧泡一?js引擎存在monitoring process進(jìn)程,會(huì)持續(xù)不斷的檢查主線程執(zhí)行棧是否為空觅廓,一旦為空鼻忠,就會(huì)去Event Queue那里檢查是否有等待被調(diào)用的函數(shù)。
2.3杈绸、JavaScript的宏任務(wù)與微任務(wù)
你是否覺得同步異步的執(zhí)行機(jī)制流程就是JavaScript執(zhí)行機(jī)制的全部帖蔓?不是的,JavaScript除了廣義上的的同步任務(wù)何異步任務(wù)瞳脓,其對任務(wù)還有更精細(xì)的定義:
macro-task(宏任務(wù)):包括整體代碼script塑娇,setTimeout,setInterval
micro-task(微任務(wù)):Promise劫侧,process.nextTick
不同類型的任務(wù)會(huì)進(jìn)入對應(yīng)的Event Queue埋酬。
事件循環(huán)的順序,決定js代碼的執(zhí)行順序烧栋。進(jìn)入整體代碼(宏任務(wù))后奇瘦,開始第一次循環(huán)。接著執(zhí)行所有的微任務(wù)劲弦。然后再次從宏任務(wù)開始耳标,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,再執(zhí)行所有的微任務(wù)邑跪。
3次坡、以實(shí)例來說明JavaScript的執(zhí)行機(jī)制
3.1、同步
console.log(1);
console.log(2);
console.log(3);
/*
執(zhí)行結(jié)果:1画畅、2砸琅、3
同步任務(wù),按照順序一步一步執(zhí)行
*/
3.2、同步和異步
console.log(1);
setTimeout(function() {
console.log(2);
},1000)
console.log(3);
/*
執(zhí)行結(jié)果:1、3句占、2
同步任務(wù),按照順序一步一步執(zhí)行
異步任務(wù)诱篷,放入消息隊(duì)列中,等待同步任務(wù)執(zhí)行結(jié)束雳灵,讀取消息隊(duì)列執(zhí)行
*/
3.3棕所、異步任務(wù)進(jìn)一步分析
console.log(1);
setTimeout(function() {
console.log(2);
},1000)
setTimeout(function() {
console.log(3);
},0)
console.log(4);
/*
猜測是:1、4悯辙、2琳省、3 但實(shí)際上是:1迎吵、4、3针贬、2
分析:
同步任務(wù)击费,按照順序一步一步執(zhí)行
異步任務(wù),當(dāng)讀取到異步任務(wù)的時(shí)候桦他,將異步任務(wù)放置到Event table(事件表格)
中荡灾,當(dāng)滿足某種條件或者說指定事情完成了(這里的是時(shí)間分別是達(dá)到了0ms和1000ms)當(dāng)指定
事件完成了才從Event table中注冊到Event Queue(事件隊(duì)列),當(dāng)同步事件完成了瞬铸,便從
Event Queue中讀取事件執(zhí)行。(因?yàn)?的事情先完成了础锐,所以先從Event table中注冊到
Event Queue中嗓节,所以先執(zhí)行的是3而不是在前面的2)
*/
3.4、宏任務(wù)和微任務(wù)
console.log(1);
setTimeout(function() {
console.log(2)
},1000);
new Promise(function(resolve) {
console.log(3);
resolve();
}
).then(function() {
console.log(4)
});
console.log(5);
/*
以同步異步的方式來判斷的結(jié)果應(yīng)該是:1皆警、3拦宣、5、2信姓、4
但是事實(shí)上結(jié)果是:1鸵隧、3、5意推、4豆瘫、2
為什么是這樣呢?因?yàn)橐酝疆惒降姆绞絹斫忉寛?zhí)行機(jī)制是不準(zhǔn)確的菊值,更加準(zhǔn)確的方式是宏任務(wù)和微任務(wù):
因此執(zhí)行機(jī)制便為:執(zhí)行宏任務(wù) ===> 執(zhí)行微任務(wù) ===> 執(zhí)行另一個(gè)宏任務(wù) ===> 不斷循環(huán)
即:在一個(gè)事件循環(huán)中外驱,執(zhí)行第一個(gè)宏任務(wù),宏任務(wù)執(zhí)行結(jié)束腻窒,執(zhí)行當(dāng)前事件循環(huán)中的微任務(wù)昵宇,
執(zhí)行完畢之后進(jìn)入下一個(gè)事件循環(huán)中,或者說執(zhí)行下一個(gè)宏任務(wù)
*/
3.5儿子、是否徹底理解JavaScript執(zhí)行機(jī)制實(shí)例
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
/*
1瓦哎、 第一輪事件循環(huán)流程分析如下:
整體script作為第一個(gè)宏任務(wù)進(jìn)入主線程,遇到console.log柔逼,輸出1蒋譬。
遇到setTimeout,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中愉适。我們暫且記為setTimeout1羡铲。
遇到process.nextTick(),其回調(diào)函數(shù)被分發(fā)到微任務(wù)Event Queue中儡毕。我們記為process1也切。
遇到Promise扑媚,new Promise直接執(zhí)行,輸出7雷恃。then被分發(fā)到微任務(wù)Event Queue中疆股。我們記為then1。
又遇到了setTimeout倒槐,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中旬痹,我們記為setTimeout2。
宏任務(wù)Event Queue 微任務(wù)Event Queue
setTimeout1 process1
setTimeout2 then1
上表是第一輪事件循環(huán)宏任務(wù)結(jié)束時(shí)各Event Queue的情況讨越,此時(shí)已經(jīng)輸出了1和7两残。
我們發(fā)現(xiàn)了process1和then1兩個(gè)微任務(wù)。
執(zhí)行process1,輸出6把跨。
執(zhí)行then1人弓,輸出8。
好了着逐,第一輪事件循環(huán)正式結(jié)束崔赌,這一輪的結(jié)果是輸出1,7耸别,6健芭,8。
2秀姐、 那么第二輪時(shí)間循環(huán)從setTimeout1宏任務(wù)開始:
首先輸出2慈迈。接下來遇到了process.nextTick(),同樣將其分發(fā)到微任務(wù)Event Queue中省有,
記為process2吩翻。new Promise立即執(zhí)行輸出4,then也分發(fā)到微任務(wù)Event Queue中锥咸,記為then2狭瞎。
宏任務(wù)Event Queue 微任務(wù)Event Queue
setTimeout2 process2
then2
第二輪事件循環(huán)宏任務(wù)結(jié)束,我們發(fā)現(xiàn)有process2和then2兩個(gè)微任務(wù)可以執(zhí)行搏予。
輸出3熊锭。
輸出5。
第二輪事件循環(huán)結(jié)束雪侥,第二輪輸出2碗殷,4,3速缨,5锌妻。
3、 第三輪事件循環(huán)開始旬牲,此時(shí)只剩setTimeout2了仿粹,執(zhí)行搁吓。
直接輸出9。
將process.nextTick()分發(fā)到微任務(wù)Event Queue中吭历。記為process3堕仔。
直接執(zhí)行new Promise,輸出11晌区。
將then分發(fā)到微任務(wù)Event Queue中摩骨,記為then3。
宏任務(wù)Event Queue 微任務(wù)Event Queue
process3
then3
第三輪事件循環(huán)宏任務(wù)執(zhí)行結(jié)束朗若,執(zhí)行兩個(gè)微任務(wù)process3和then3恼五。
輸出10。
輸出12哭懈。
第三輪事件循環(huán)結(jié)束灾馒,第三輪輸出9,11银伟,10,12绘搞。
整段代碼彤避,共進(jìn)行了三次事件循環(huán),完整的輸出為1夯辖,7琉预,6,8蒿褂,2圆米,4,3啄栓,5娄帖,9,11昙楚,10近速,12。
*/
4堪旧、相關(guān)概念
4.1削葱、JS為什么是單線程的?
JavaScript語言的一大特點(diǎn)就是單線程淳梦,也就是說析砸,同一個(gè)時(shí)間只能做一件事。那么爆袍,為什么JavaScript不能有多個(gè)線程呢首繁?這樣能提高效率啊作郭。
JavaScript的單線程,與它的用途有關(guān)蛮瞄。作為瀏覽器腳本語言所坯,JavaScript的主要用途是與用戶互動(dòng),以及操作DOM挂捅。這決定了它只能是單線程芹助,否則會(huì)帶來很復(fù)雜的同步問題。比如闲先,假定JavaScript同時(shí)有兩個(gè)線程状土,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn)伺糠,這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)蒙谓?
所以,為了避免復(fù)雜性训桶,從一誕生累驮,JavaScript就是單線程,這已經(jīng)成了這門語言的核心特征舵揭,將來也不會(huì)改變谤专。
為了利用多核CPU的計(jì)算能力,HTML5提出Web Worker標(biāo)準(zhǔn)午绳,允許JavaScript腳本創(chuàng)建多個(gè)線程置侍,但是子線程完全受主線程控制,且不得操作DOM拦焚。所以蜡坊,這個(gè)新標(biāo)準(zhǔn)并沒有改變JavaScript單線程的本質(zhì)。
4.2赎败、JS為什么需要異步?
如果JS中不存在異步秕衙,只能自上而下執(zhí)行,如果上一行解析時(shí)間很長僵刮,那么下面的代碼就會(huì)被阻塞灾梦。 對于用戶而言,阻塞就意味著"卡死"妓笙,這樣就導(dǎo)致了很差的用戶體驗(yàn)若河。
4.3、JS單線程又是如何實(shí)現(xiàn)異步的呢寞宫?
既然JS是單線程的萧福,只能在一條線程上執(zhí)行,又是如何實(shí)現(xiàn)的異步呢辈赋?
是通過的事件循環(huán)(event loop)鲫忍,理解了event loop機(jī)制膏燕,就理解了JS的執(zhí)行機(jī)制。
4.4悟民、任務(wù)隊(duì)列
"任務(wù)隊(duì)列"是一個(gè)事件的隊(duì)列(也可以理解成消息的隊(duì)列)坝辫,IO設(shè)備完成一項(xiàng)任務(wù),就在"任務(wù)隊(duì)列"中添加一個(gè)事件射亏,表示相關(guān)的異步任務(wù)可以進(jìn)入"執(zhí)行棧"了近忙。主線程讀取"任務(wù)隊(duì)列",就是讀取里面有哪些事件智润。
"任務(wù)隊(duì)列"中的事件及舍,除了IO設(shè)備的事件以外,還包括一些用戶產(chǎn)生的事件(比如鼠標(biāo)點(diǎn)擊窟绷、頁面滾動(dòng)等等)锯玛。只要指定過回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入"任務(wù)隊(duì)列"兼蜈,等待主線程讀取攘残。
所謂"回調(diào)函數(shù)"(callback),就是那些會(huì)被主線程掛起來的代碼为狸。異步任務(wù)必須指定回調(diào)函數(shù)歼郭,當(dāng)主線程開始執(zhí)行異步任務(wù),就是執(zhí)行對應(yīng)的回調(diào)函數(shù)钥平。
"任務(wù)隊(duì)列"是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)实撒,排在前面的事件姊途,優(yōu)先被主線程讀取涉瘾。主線程的讀取過程基本上是自動(dòng)的,只要執(zhí)行棧一清空捷兰,"任務(wù)隊(duì)列"上第一位的事件就自動(dòng)進(jìn)入主線程立叛。但是,由于存在后文提到的"定時(shí)器"功能贡茅,主線程首先要檢查一下執(zhí)行時(shí)間秘蛇,某些事件只有到了規(guī)定的時(shí)間,才能返回主線程顶考。
讀取到一個(gè)異步任務(wù)赁还,首先是將異步任務(wù)放進(jìn)事件表格(Event table)中,當(dāng)放進(jìn)事件表格中的異步任務(wù)完成某種事情或者說達(dá)成某些條件(如setTimeout事件到了驹沿,鼠標(biāo)點(diǎn)擊了艘策,數(shù)據(jù)文件獲取到了)之后,才將這些異步任務(wù)推入事件隊(duì)列(Event Queue)中渊季,這時(shí)候的異步任務(wù)才是執(zhí)行棧中空閑的時(shí)候才能讀取到的異步任務(wù)朋蔫。
4.5罚渐、Event Loop
主線程從"任務(wù)隊(duì)列"中讀取事件,這個(gè)過程是循環(huán)不斷的驯妄,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))荷并。
Event Loop是javascript的執(zhí)行機(jī)制
4.6、setTimeout(fn,0)
setTimeout(fn,0)的含義是青扔,指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行源织,也就是說,盡可能早得執(zhí)行赎懦。它在"任務(wù)隊(duì)列"的尾部添加一個(gè)事件雀鹃,因此要等到同步任務(wù)和"任務(wù)隊(duì)列"現(xiàn)有的事件都處理完,才會(huì)得到執(zhí)行励两。
HTML5標(biāo)準(zhǔn)規(guī)定了setTimeout()的第二個(gè)參數(shù)的最小值(最短間隔)黎茎,不得低于4毫秒,如果低于這個(gè)值当悔,就會(huì)自動(dòng)增加傅瞻。在此之前,老版本的瀏覽器都將最短間隔設(shè)為10毫秒盲憎。另外嗅骄,對于那些DOM的變動(dòng)(尤其是涉及頁面重新渲染的部分),通常不會(huì)立即執(zhí)行饼疙,而是每16毫秒執(zhí)行一次溺森。這時(shí)使用requestAnimationFrame()的效果要好于setTimeout()。
需要注意的是窑眯,setTimeout()只是將事件插入了"任務(wù)隊(duì)列"屏积,必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)磅甩。要是當(dāng)前代碼耗時(shí)很長炊林,有可能要等很久,所以并沒有辦法保證卷要,回調(diào)函數(shù)一定會(huì)在setTimeout()指定的時(shí)間執(zhí)行渣聚。
參考網(wǎng)址
https://juejin.im/post/59e85eebf265da430d571f89
https://www.cnblogs.com/MasterYao/p/5563725.html
https://blog.csdn.net/highboys/article/details/79110116