JS是單線程的(所謂單線程褐健,是指在JS引擎中負(fù)責(zé)解釋和執(zhí)行JavaScript代碼的線程只有一個(gè)韩肝,叫主線程),就是說JS同一時(shí)間只能處理一件事影晓。那么就可能出現(xiàn)這種情況:一件事需要花費(fèi)很長(zhǎng)時(shí)間處理迷守,后面的事情只能等待犬绒,體驗(yàn)就非常差。
所以JS中將執(zhí)行的任務(wù)分為兩類:同步任務(wù)和異步任務(wù)兑凿。
同步任務(wù)懂更,同步任務(wù)指的是眨业,發(fā)出調(diào)用立即獲得結(jié)果的為同步任務(wù)急膀。同步任務(wù)會(huì)在調(diào)用之后一直等待沮协,直到返回結(jié)果。
異步任務(wù)卓嫂,異步任務(wù)指的是慷暂,發(fā)出調(diào)用,但無法立即獲得結(jié)果晨雳,需要額外的操作才能得到預(yù)期的結(jié)果的為異步任務(wù)行瑞。調(diào)用之后和拿到結(jié)果之間,可以進(jìn)行其他操作餐禁。
JS運(yùn)行環(huán)境運(yùn)行機(jī)制
- 所有同步任務(wù)都在主線程上執(zhí)行血久,形成一個(gè)執(zhí)行棧;
- 主線程之外帮非,還存在一個(gè)任務(wù)隊(duì)列(一個(gè)先進(jìn)先出的隊(duì)列氧吐,里面放著各種事件)。只要異步任務(wù)有了運(yùn)行結(jié)果末盔,就在任務(wù)隊(duì)列之中放置一個(gè)事件筑舅;
- 一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列陨舱,看看里面有哪些事件翠拣。那些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài)游盲,進(jìn)入執(zhí)行棧误墓,開始執(zhí)行;
- 主線程不斷重復(fù)上面的第三步益缎;
所以執(zhí)行棧中的代碼(同步任務(wù))谜慌,總是在讀取任務(wù)隊(duì)列(異步任務(wù))之前執(zhí)行
事件和回調(diào)函數(shù)
事件,所謂的事件驅(qū)動(dòng)就是將一切抽象為事件链峭。I/O操作完成是一個(gè)事件畦娄,用戶點(diǎn)擊是一個(gè)事件,Ajax完成是一個(gè)事件弊仪,一個(gè)圖片加載完成是一個(gè)事件熙卡,當(dāng)產(chǎn)生事件后,這個(gè)事件會(huì)被放入任務(wù)隊(duì)列中等待被處理励饵。
回調(diào)函數(shù)驳癌,與事件關(guān)聯(lián)的函數(shù),會(huì)在執(zhí)行事件時(shí)調(diào)用役听。
setTimeout(fn, 1000); // 例如setTimeout時(shí)間到了就會(huì)對(duì)應(yīng)一個(gè)事件颓鲜,而fn就是事件執(zhí)行時(shí)調(diào)用的回調(diào)函數(shù)
事件循環(huán)(event loop)
主線程只會(huì)做一件事情表窘,就是從任務(wù)隊(duì)列里面取事件、執(zhí)行事件甜滨,再取事件乐严、再執(zhí)行。當(dāng)任務(wù)隊(duì)列為空時(shí)衣摩,就會(huì)等待直到任務(wù)隊(duì)列變成非空昂验。而且主線程只有在將當(dāng)前的事件執(zhí)行完成后,才會(huì)去取下一個(gè)事件艾扮。這種機(jī)制就叫做事件循環(huán)機(jī)制既琴,取一個(gè)消息并執(zhí)行的過程叫做一次循環(huán)。
setTimeout(function () {
console.log(1);
}, 0);
console.log(2);
// 2
// 1
// setTimeout(..) 并不是直接把回調(diào)函數(shù)掛在任務(wù)隊(duì)列中泡嘴。它所做的是設(shè)定一個(gè)定時(shí)器甫恩。當(dāng)定時(shí)器到時(shí)后,相當(dāng)于產(chǎn)生了一個(gè)時(shí)間到了的事件酌予,這個(gè)事件進(jìn)入任務(wù)隊(duì)列磺箕。這樣,下個(gè)事件循環(huán)主線程會(huì)取出并執(zhí)行這個(gè)事件的回調(diào)函數(shù)
// 因?yàn)橹骶€程會(huì)先執(zhí)行完執(zhí)行棧中的同步任務(wù)霎终,才會(huì)去任務(wù)隊(duì)列提取事件滞磺,所以異步任務(wù)的回調(diào)函數(shù)總是在同步任務(wù)知乎執(zhí)行
// 但如果任務(wù)隊(duì)列中已經(jīng)有多個(gè)事件排隊(duì),那么就會(huì)導(dǎo)致setTimeOut的函數(shù)延后運(yùn)行莱褒,這就是為什么setTimeOut和setInterval不準(zhǔn)確的原因
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date());
}, 1000);
}
console.log(new Date());
// 先輸出一個(gè)日期击困,一秒后再幾乎同時(shí)輸出五個(gè)日期,但可以看出五個(gè)日期有細(xì)微的差別广凸,這些誤差就是因?yàn)槿蝿?wù)隊(duì)列中有多個(gè)事件排隊(duì)導(dǎo)致后邊事件延遲的結(jié)果