一篇文章弄懂JavaScript事件執(zhí)行機(jī)制

JavaScript 是一門(mén)單線程的語(yǔ)言晌砾?

首先對(duì)于一個(gè)學(xué)前端的人來(lái)說(shuō)凛捏,肯定是聽(tīng)過(guò)這樣一句話(huà)担忧, JavaScript 是一門(mén)單線程的語(yǔ)言 。一些人聽(tīng)到這里的時(shí)候就會(huì)想坯癣,啊瓶盛,JS這語(yǔ)言不太行啊,只能單線程示罗,太垃圾了惩猫。有這種想法人,只能說(shuō)他還不了解JS蚜点。說(shuō)JS是一門(mén)單線程的語(yǔ)言轧房,并不是說(shuō)它本身有什么缺陷,只能和單線程扯上聯(lián)系绍绘,多線程這種能大大提高效率的事奶镶,和它沒(méi)什么關(guān)系。那你就錯(cuò)了陪拘。如今有了node厂镇,js不僅能在瀏覽器上執(zhí)行了,在后端用js寫(xiě)個(gè)多線程還不是簡(jiǎn)簡(jiǎn)單單的事情么左刽。那你可能就要問(wèn)了捺信,那為什么JS在瀏覽器中就非要單線程執(zhí)行呢? 其實(shí)JavaScript的單線程悠反,是與它的功能相關(guān)的残黑。作為瀏覽器腳本語(yǔ)言馍佑,JavaScript的主要用途是頁(yè)面與用戶(hù)的交互,操作DOM元素梨水。這就決定了它只能是單線程的拭荤,否則會(huì)帶來(lái)很復(fù)雜的問(wèn)題。比如疫诽,JavaScript如果說(shuō)是多線程的舅世,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn)奇徒,那么瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)呢雏亚? 可是,單線程就意味著同一個(gè)時(shí)間只能做一件事摩钙,如果都是同步執(zhí)行下去的話(huà)罢低,就會(huì)出現(xiàn)阻塞的現(xiàn)象,比如發(fā)送一個(gè)ajax請(qǐng)求胖笛,在等待服務(wù)器返回?cái)?shù)據(jù)的過(guò)程中网持,其它什么事情都執(zhí)行不了,這種情況想想也是災(zāi)難性的长踊。所以功舀,在JS中,定時(shí)器身弊,ajax請(qǐng)求辟汰,事件綁定等操作都是異步執(zhí)行的。至于單線程中怎么進(jìn)行異步操作阱佛,這就要好好說(shuō)一說(shuō)我們今天的主角帖汞,JavaScript的執(zhí)行機(jī)制以及其中的事件循環(huán)(Event Loop)了。

初步了解JavaScript的執(zhí)行機(jī)制

在瀏覽器開(kāi)始解析js文件的時(shí)候瘫絮,會(huì)有一個(gè)主執(zhí)行棧涨冀,js代碼從上到下在主執(zhí)行棧中執(zhí)行(之前會(huì)有變量提升,函數(shù)聲明提升一系列操作)麦萤,遇到函數(shù)調(diào)用的時(shí)候,會(huì)將函數(shù)放到調(diào)用棧(call stack)中執(zhí)行(調(diào)用棧還是在主線程壮莹,執(zhí)行順序中和全局上下文執(zhí)行順序基本相同姻檀,至于函數(shù)執(zhí)行上下文命满,函數(shù)調(diào)用完后彈出調(diào)用椥灏妫或繼續(xù)保留形成閉包就不在這里詳細(xì)闡述了)胶台,在沒(méi)有遇到異步任務(wù)之前歼疮,js就是這樣一步步向下執(zhí)行的诈唬。當(dāng)遇到異步函數(shù)的時(shí)候,并不會(huì)將這個(gè)任務(wù)放到直接放到主線程中執(zhí)行铸磅,而是先把它放到任務(wù)隊(duì)列( task queue )中赡矢,等到主線程的所有任務(wù)執(zhí)行完后,就會(huì)從任務(wù)隊(duì)列中拿出一個(gè)任務(wù)阅仔,將其代碼放到主線程中執(zhí)行吹散,至于代碼里面又存在的異步任務(wù)八酒,依舊是先放到任務(wù)隊(duì)列中去,就是這樣從任務(wù)隊(duì)列中拿任務(wù)界轩,在主線程中執(zhí)行闭树,遇到異步任務(wù)再放到任務(wù)隊(duì)列中這樣的一個(gè)循環(huán)過(guò)程,就成為事件循環(huán)(Event Loop)报辱。聽(tīng)起來(lái)是不是還挺簡(jiǎn)單的,別著急幅疼,里面還有一些更加細(xì)節(jié)的東西要說(shuō)的昼接。

異步任務(wù)

首先我們要先明確異步任務(wù)都有哪些:

1、定時(shí)器都是異步操作
2逐工、事件綁定都是異步操作
3漂辐、AJAX中的操作
4、回調(diào)函數(shù)可以理解為異步(不是嚴(yán)謹(jǐn)?shù)漠惒讲僮鳎?/p>

其中髓涯,為了防止回調(diào)地獄的形成,es6又提供了promise和async/await解決方案(用的最多的兩種方案)蚓再,對(duì)于promise成功后的then()和await等待結(jié)果的任務(wù)的操作,也是要進(jìn)入任務(wù)隊(duì)列( task queue )摘仅。這時(shí)就又要提到任務(wù)隊(duì)列中又細(xì)分的宏任務(wù)(macrotask)和微任務(wù)(microtask)了

詳細(xì)認(rèn)知JavaScript的執(zhí)行機(jī)制

對(duì)于我們熟知的異步任務(wù),是放入宏任務(wù)隊(duì)列中的实檀,promise狀態(tài)為resolve后的then()和await等待結(jié)果的任務(wù)是放到微任務(wù)隊(duì)列的膳犹。每次主線程的代碼執(zhí)行完以后,是會(huì)先把微任務(wù)隊(duì)列中的任務(wù)給執(zhí)行完然后才會(huì)拿宏任務(wù)隊(duì)列中的任務(wù)放到主線程中執(zhí)行的须床。再結(jié)合上面說(shuō)的執(zhí)行順序,js代碼的執(zhí)行機(jī)制就很明確了钠惩。

為了能使你更好更快的理解族阅,我將用提煉的語(yǔ)言給你再總解一遍,首先愧沟,你要先知道我提到的幾個(gè)術(shù)語(yǔ)的含義:

1.解決全局任務(wù):是指執(zhí)行整個(gè)js文件鲤遥,其中同步任務(wù)放到主線程中執(zhí)行,異步任務(wù)放到對(duì)應(yīng)的任務(wù)隊(duì)列中混坞。當(dāng)主線程中將js文件中的的同步任務(wù)執(zhí)行完后钢坦,就算解決了解決全局任務(wù)。

2.解決一個(gè)微任務(wù):是指執(zhí)行一個(gè)微任務(wù)中的代碼爹凹,將這個(gè)微任務(wù)代碼中的同步任務(wù)放到主線程中執(zhí)行逛万,異步任務(wù)放到對(duì)應(yīng)的任務(wù)隊(duì)列中。當(dāng)主線程中將這個(gè)微任務(wù)中的同步任務(wù)執(zhí)行完后宇植,就算解決了一個(gè)微任務(wù)。

3.解決一個(gè)宏任務(wù):將一個(gè)宏任務(wù)放在主線程中執(zhí)行( 宏任務(wù)肯定是指定了回調(diào)函數(shù)的忙上,主線程執(zhí)行異步任務(wù)闲坎,其實(shí)就是執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù))對(duì)于這個(gè)宏任務(wù)中新形成的異步任務(wù)放在對(duì)應(yīng)的任務(wù)隊(duì)列中。當(dāng)主線程將這個(gè)宏任務(wù)執(zhí)行完以后梗逮,就算解決了一個(gè)宏任務(wù)绣溜。

總結(jié)一下就是:
1.解決全局任務(wù)(要明確,只要執(zhí)行js文件底哗,就會(huì)而且只會(huì)解決一次全局任務(wù))
2.依次解決微任務(wù)(解決一個(gè)微任務(wù)后锚沸,再解決下一個(gè)微任務(wù),直到微任務(wù)隊(duì)列中的所有微任務(wù)都解決完)
3.解決一個(gè)宏任務(wù)(可能會(huì)在任務(wù)隊(duì)列中添加了新的微任務(wù)和宏任務(wù))
4.重復(fù)2.3步操作前标,知道解決所有的任務(wù)
5.結(jié)束

其中恬叹,要注意的是,promise和async本身并不是直接放到微任務(wù)隊(duì)列的唯鸭。

//promise
new Promise((resolve,reject)=>{
    console.log(1); //在new Promies的時(shí)候硅确,會(huì)直接執(zhí)行里面的代碼的
    resolve('成功的數(shù)據(jù)') //then中的任務(wù)是在執(zhí)行resolve的時(shí)候才放到微任務(wù)隊(duì)列的
    console.log(2)
}).then(res=>{      
    console.log(3)
})
console.log(4)
//運(yùn)行的結(jié)果是:1 2 4 3

//async

async function aaa(){
    console.log(1)  //不是說(shuō)整個(gè)async都會(huì)放到微任務(wù)隊(duì)列中,等待前面的部分是同步執(zhí)行的
    let n = await async1() //在await獲取到值后缭付,將后面的代碼放到微任務(wù)隊(duì)列中循未,即使await等待的函數(shù)里面并沒(méi)有異步操作
    console.log(2)
}
function async1(){
    console.log(3)
}
aaa() //運(yùn)行結(jié)果是:1 3 2

看完整個(gè)上面的文字描述,現(xiàn)在對(duì)js的整個(gè)執(zhí)行機(jī)制你心里面應(yīng)該有一個(gè)自己的認(rèn)知了绣檬,那帶著你自己的想法,看一看下面的代碼娇未,通過(guò)代碼零抬,相信你會(huì)對(duì)這個(gè)過(guò)程的認(rèn)知更加清晰,甚至可能會(huì)打破你已有的認(rèn)知平夜,讓你對(duì)整個(gè)過(guò)程有一個(gè)更加準(zhǔn)確的理解。

代碼通關(guān)

答案在最后

1.   console.log('1');
2.   setTimeout(function() {
3.        console.log('2');
4.        new Promise(function(resolve) {
5.            console.log('3');
6.            resolve();
7.        }).then(function() {
8.            console.log('4')
9.        })
10.    },0)
11.    new Promise(function(resolve) {
12.        console.log('5');
13.        resolve();
14.    }).then(function() {
15.        console.log('6')
16.    })
17.    setTimeout(function() {
18.        console.log('7');
19.        new Promise(function(resolve) {
20.            console.log('8');
21.            resolve();
22.        }).then(function() {
23.            console.log('9')
24.        })
25.        console.log('10')
26.    },0)
27.    console.log('11')

看起來(lái)是不是有點(diǎn)棘手嚼松,不要慌锰扶,跟著我一步步的把它給吃掉。

macrotask(宏任務(wù)隊(duì)列):[]; microtask(微任務(wù)隊(duì)列):[];console(控制臺(tái)輸出):[]

第1行坷牛,最先輸出1是毫無(wú)問(wèn)題的(console:[1])

第2行京闰,遇到定時(shí)器,直接放到宏任務(wù)隊(duì)列中蹂楣,然后跳到11行。(macrotask[第2行])

第11行肄扎,創(chuàng)建一個(gè)promise實(shí)例赁酝,會(huì)直接執(zhí)行傳進(jìn)來(lái)的函數(shù)
第12行,輸出5(console:[1衡载,5])
第13行隙袁,將這個(gè)promise實(shí)例的狀態(tài)設(shè)置為resolve弃榨,同時(shí)將其then里面的函數(shù)放到微任務(wù)隊(duì)列中(microtask:[第14行])

第17行猜揪,遇到定時(shí)器,直接放到宏任務(wù)隊(duì)列中,然后跳到27行划咐。(macrotask[第2行褐缠,第17行])

第21行,輸出11(console:[1队魏,5,11])
此時(shí)主線程任務(wù)執(zhí)行完畢(解決了全局任務(wù))官帘,要開(kāi)始依次解決微任務(wù)昧谊。跳到第14行

第14,15行 輸出6(console:[1涌哲,5尚镰,11,6]初烘,microtask:[])
此時(shí)主線程任務(wù)又執(zhí)行完畢(解決了一個(gè)微任務(wù))同時(shí)微任務(wù)隊(duì)列也被清空敞曹,開(kāi)始解決宏任務(wù)。跳到第2行

第2澳迫,3行 輸出2(console:[1橄登,5讥此,11谣妻,6,2])

第4行 創(chuàng)建一個(gè)promise實(shí)例他巨,會(huì)直接執(zhí)行傳進(jìn)來(lái)的函數(shù)
第5行 輸出3(console:[1减江,5,11辈灼,6巡莹,2,5降宅,3])
第6行 將這個(gè)promise實(shí)例的狀態(tài)設(shè)置為resolve钉鸯,同時(shí)將其then里面的函數(shù)放到微任務(wù)隊(duì)列中(microtask:[第7行],macrotask[第17行])
此時(shí)主線程再次實(shí)行完畢(解決了一個(gè)宏任務(wù))贸营,再執(zhí)行下一個(gè)宏任務(wù)之前岩睁,要先把微任務(wù)清空,所以跳到第7行捕儒,解決微任務(wù)

第7,8行:輸出4(console:[1阎毅,5点弯,11,6狼钮,2,5莲镣,3涎拉,4])
此時(shí)主線程再次執(zhí)行完畢(解決了一個(gè)微任務(wù)),同時(shí)微任務(wù)隊(duì)列也被清空区岗,開(kāi)始解決宏任務(wù)毁枯,跳到第17行

17叮称,18行輸出7(console:[1,5赂韵,11挠蛉,6,2质涛,5掰担,3,4毡代,7])

第19行 創(chuàng)建一個(gè)promise實(shí)例勺疼,會(huì)直接執(zhí)行傳進(jìn)來(lái)的函數(shù)
第20行 輸出8(console:[1,5酪耕,11耕肩,6问潭,2婚被,5,3灾茁,4谷炸,7旬陡,8])
第21行 將這個(gè)promise實(shí)例的狀態(tài)設(shè)置為resolve,同時(shí)將其then里面的函數(shù)放到微任務(wù)隊(duì)列中(microtask:[第22行])

第25行:輸出10(console:[1驶睦,5匿醒,11,6廉羔,2憋他,5,3举瑰,4此迅,7,8忍些,10])
此時(shí)主線程再次執(zhí)行完畢(解決了一個(gè)微任務(wù))坎怪,微任務(wù)隊(duì)列中又有了新任務(wù),繼續(xù)執(zhí)行嘁酿,跳到第22行

第22,23行:輸出9(console:[1闹司,5游桩,11,6盹憎,2铐刘,5,3奶稠,4膝蜈,7,8洽沟,10慷暂,9])

一步步下來(lái)是不是很清爽芹关,好像也就那么回事,相信現(xiàn)在你對(duì)整個(gè)流程已經(jīng)熟記在心了诗祸,接下來(lái)再做一道題轴总,展現(xiàn)一下自己的實(shí)力吧

1.    async function async1() {
2.        console.log('1');
3.        await async2();
4.        console.log('2');
5.    }
6.
7.    async function async2() {
8.        console.log('3');
9.    }
10.    console.log('4');
11.
12.    setTimeout(function() {
13.        console.log('5');
14.    }, 0)
15.    async1();
16.
17.    new Promise(function(resolve) {
18.        console.log('6');
19.        resolve();
20.    }).then(function() {
21.        console.log('7');
22.    });
23.    console.log('8');
macrotask(宏任務(wù)隊(duì)列):[]; microtask(微任務(wù)隊(duì)列):[];console(控制臺(tái)輸出):[]

1-9.定義了兩個(gè)異步函數(shù)怀樟,并沒(méi)有執(zhí)行,直接跳過(guò)

10.輸出4(console:[4])

12.定時(shí)器械荷,放到宏任務(wù)隊(duì)列中(macrotask[第12行])

15.執(zhí)行async1,跳到第1行

2.輸出1(console:[4痹兜,1])

3.執(zhí)行async2关拒,跳到第7行

7-8.輸出3(console:[4,1谐算,3])
回到第3行归露,將后面的代碼放到微任務(wù)隊(duì)列中(microtask:[第4行]),此時(shí)async1函數(shù)執(zhí)行完畢恐锦,回到調(diào)用函數(shù)的地方15行疆液,繼續(xù)執(zhí)行后面的代碼

//這一組邏輯大家應(yīng)該已經(jīng)很熟悉了
17.創(chuàng)建一個(gè)promise實(shí)例堕油,會(huì)直接執(zhí)行傳進(jìn)來(lái)的函數(shù)
18.輸出6(console:[4,1卜录,3眶明,6])
19.將這個(gè)promise實(shí)例的狀態(tài)設(shè)置為resolve丑瞧,同時(shí)將其then里面的函數(shù)放到微任務(wù)隊(duì)列中(microtask:[第4行犬辰,第20行])

23.輸出8(console:[4幌缝,1,3浴栽,6,8])
此時(shí)主線程任務(wù)執(zhí)行完畢(解決了全局任務(wù))被廓,要開(kāi)始依次解決微任務(wù)萝玷。跳到第4行

4.輸出2(console:[4,1蜓斧,3睁冬,6,8直奋,2])
時(shí)主線程再次執(zhí)行完畢(解決了一個(gè)微任務(wù))脚线,微任務(wù)隊(duì)列中還有任務(wù)弥搞,繼續(xù)執(zhí)行,跳到第20行

20-21 輸出7(console:[4,1肛度,3投慈,6,8加袋,2抱既,7])
此時(shí)主線程任務(wù)又執(zhí)行完畢(解決了一個(gè)微任務(wù))同時(shí)微任務(wù)隊(duì)列也被清空,開(kāi)始解決宏任務(wù)蚀之。跳到第12行

12-13 輸出5 (console:[4,1寿谴,3失受,6拂到,8,2惠桃,7辖试,5])

相信,將這兩題的過(guò)程一步步的搞明白罐孝,js的執(zhí)行過(guò)程對(duì)你來(lái)說(shuō)已經(jīng)不是什么問(wèn)題了呐馆。

答案:
第一題輸出順序:1 5 11 6 2 3 4 7 8 10 9
第二題輸出順序:4,1莲兢,3汹来,6,8改艇,2收班,7,5

答錯(cuò)了不要緊谒兄,相信我的答案分析一定能讓你搞明白到底是哪里出了問(wèn)題摔桦。答對(duì)了,那就恭喜你了承疲,你對(duì)js執(zhí)行的機(jī)制應(yīng)該掌握的是很不錯(cuò)了,但還是可以看一看答案分析哦燕鸽,也許你和我有不一樣的理解兄世,那你可一定要想辦法聯(lián)系我一下哦。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末啊研,一起剝皮案震驚了整個(gè)濱河市御滩,隨后出現(xiàn)的幾起案子鸥拧,更是在濱河造成了極大的恐慌,老刑警劉巖艾恼,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件住涉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡钠绍,警方通過(guò)查閱死者的電腦和手機(jī)舆声,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)柳爽,“玉大人媳握,你說(shuō)我怎么就攤上這事×赘” “怎么了蛾找?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赵誓。 經(jīng)常有香客問(wèn)我打毛,道長(zhǎng),這世上最難降的妖魔是什么俩功? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任幻枉,我火速辦了婚禮,結(jié)果婚禮上诡蜓,老公的妹妹穿的比我還像新娘熬甫。我一直安慰自己,他們只是感情好蔓罚,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布椿肩。 她就那樣靜靜地躺著,像睡著了一般豺谈。 火紅的嫁衣襯著肌膚如雪郑象。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天茬末,我揣著相機(jī)與錄音扣唱,去河邊找鬼。 笑死团南,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炼彪。 我是一名探鬼主播吐根,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼辐马!你這毒婦竟也來(lái)了拷橘?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冗疮,沒(méi)想到半個(gè)月后萄唇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡术幔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年另萤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诅挑。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡四敞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拔妥,到底是詐尸還是另有隱情,我是刑警寧澤没龙,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布硬纤,位于F島的核電站,受9級(jí)特大地震影響伐蒂,放射性物質(zhì)發(fā)生泄漏肛鹏。R本人自食惡果不足惜在扰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一芒珠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧皱卓,春花似錦娜汁、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)峡扩。三九已至障本,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巍佑,已是汗流浹背寄悯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工猜旬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洒擦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓秦踪,卻偏偏與公主長(zhǎng)得像椅邓,于是被迫代替她去往敵國(guó)和親昧狮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351