[Toc]
為什么js是單線(xiàn)程?
- 因?yàn)閖s的主要用途是在瀏覽器中操作DOM, 這決定了它只能是單線(xiàn)程,否則可能會(huì)造成同步的問(wèn)題. 試想一個(gè)線(xiàn)程對(duì)某個(gè)DOM節(jié)點(diǎn)上修改內(nèi)容, 另一個(gè)線(xiàn)程刪除這個(gè)DOM節(jié)點(diǎn), 那么兩個(gè)線(xiàn)程將會(huì)造成沖突.
- 此外html5還新增了web worker, 它允許js創(chuàng)建多個(gè)線(xiàn)程. 但是子線(xiàn)程完全受主線(xiàn)程控制, 且子線(xiàn)程不允許操作DOM, 它沒(méi)有改變js單線(xiàn)程的本質(zhì).
- 單線(xiàn)程必然造成阻塞, 但是js為了提高線(xiàn)程的利用率, 將需要耗費(fèi)時(shí)間的操作(比如ajax請(qǐng)求,dom操作等)交給宿主的webAPIs處理, 并繼續(xù)執(zhí)行接下來(lái)的代碼, 由宿主負(fù)責(zé)接收事件, 并將接收到的事件丟進(jìn)事件循環(huán)隊(duì)列, 等待主線(xiàn)程處理完執(zhí)行棧中最后一個(gè)作用域中的代碼后, 再將事件循環(huán)隊(duì)列中的事件的回調(diào)函數(shù)壓入執(zhí)行棧執(zhí)行.
事件循環(huán)
-
js代碼是分塊的, 且只有一個(gè)塊是現(xiàn)在執(zhí)行(執(zhí)行棧中最頂部的代碼), 其余的則會(huì)在將來(lái)執(zhí)行. 最常見(jiàn)的塊單位是函數(shù).
- 為了方便理解可以將現(xiàn)在執(zhí)行代碼理解為同步代碼, 將來(lái)執(zhí)行代碼理解位異步代碼.
任何時(shí)候, 只要把一段代碼包裝成函數(shù), 并指定它在響應(yīng)某個(gè)事件(定時(shí)器, 鼠標(biāo)點(diǎn)擊, ajax響應(yīng)等)時(shí)執(zhí)行, 就是在代碼中創(chuàng)建了一個(gè)將來(lái)執(zhí)行的塊.
事件循環(huán)(eventLoop)是一種機(jī)制, 由宿主提供, 用來(lái)調(diào)用js引擎處理程序中的每個(gè)將來(lái)執(zhí)行塊的執(zhí)行
-
事件循環(huán)偽代碼:
var eventLoop=[] var event while(true){ // 一次tick if(eventLoop.length>0){ event=eventLoop.shift() try{ event() }catch(err){ reportError(err) } } }不太完善的補(bǔ)充: 只有當(dāng)前執(zhí)行棧中的代碼全部執(zhí)行完畢后才會(huì)進(jìn)行事件循環(huán), eventLoop隊(duì)列中的事件添加由宿主的webAPIs決定
setTimeout()做的是: 在設(shè)定的delay時(shí)間后, 將回調(diào)函數(shù)插入事件循環(huán)隊(duì)列中. 該回調(diào)函數(shù)必須等到前面的項(xiàng)目處理完畢之后才會(huì)被調(diào)用, 也因此setTimeout()的回調(diào)函數(shù)的調(diào)用時(shí)間無(wú)法確定.
任務(wù)隊(duì)列(job queue)
它是掛在事件循環(huán)隊(duì)列的每一個(gè)tick之后的一個(gè)隊(duì)列. 在事件循環(huán)的每個(gè)tick中, 可能出現(xiàn)的Promise.then(), MutationObserver異步任務(wù)不會(huì)導(dǎo)致一個(gè)新的事件添加到事件循環(huán)隊(duì)列末尾, 而是在當(dāng)前tick的任務(wù)隊(duì)列末尾再添加一個(gè)任務(wù).
console.log(1) // 同步代碼,按順序調(diào)用 setTimeout(()=>{ console.log(2) // 它被加入到事件循環(huán)隊(duì)列中,在下一個(gè)tick中調(diào)用 },0) new Promise(function(resolve,reject){ console.log(3) // new Promise不是異步代碼, Promise.then()才是異步代碼 resolve() }).then(function(){ console.log(4) // 它被加入到任務(wù)隊(duì)列, 本次tick的末尾 }) console.log(5) // 同步代碼,按順序調(diào)用 // 打印 1 3 5 4 2不完善的補(bǔ)充: 一些文檔中將Promise.then與MutationObserver列為微任務(wù)隊(duì)列, 其它的異步代碼列為宏任務(wù)隊(duì)列. 個(gè)人理解為: 在每一個(gè)tick中, 微任務(wù)隊(duì)列會(huì)在同步代碼執(zhí)行后再執(zhí)行. 宏任務(wù)隊(duì)列丟進(jìn)eventLoop中等待調(diào)用.
setTimeout(() => { console.log(1) // tick1 setTimeout(() => { console.log(2) // tick3 setTimeout(() => { console.log(3) // tick5 }, 0); }, 0); setTimeout(() => { console.log(4) // tick4 }, 0); }, 0); setTimeout(() => { console.log(5) // tick2 }, 0); // 打印1 5 2 4 3