為什么JavaScript是單線程
JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。
JavaScript的單線程,與它的用途有關(guān)。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復(fù)雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容,另一個線程刪除了這個節(jié)點,這時瀏覽器應(yīng)該以哪個線程為準(zhǔn)?
所以,為了避免復(fù)雜性,從一誕生,JavaScript就是單線程,這已經(jīng)成了這門語言的核心特征,將來也不會改變。
為了利用多核CPU的計算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標(biāo)準(zhǔn)并沒有改變JavaScript單線程的本質(zhì)。
任務(wù)隊列
單線程就意味著,所有任務(wù)需要排隊,前一個任務(wù)結(jié)束,才會執(zhí)行后一個任務(wù)。如果前一個任務(wù)耗時很長,后一個任務(wù)就不得不一直等著。
如果排隊是因為計算量大,CPU忙不過來,倒也算了,但是很多時候CPU是閑著的,因為IO設(shè)備(輸入輸出設(shè)備)很慢(比如Ajax操作從網(wǎng)絡(luò)讀取數(shù)據(jù)),不得不等著結(jié)果出來,再往下執(zhí)行。
JavaScript語言的設(shè)計者意識到,這時主線程完全可以不管IO設(shè)備,掛起處于等待中的任務(wù),先運行排在后面的任務(wù)。等到IO設(shè)備返回了結(jié)果,再回過頭,把掛起的任務(wù)繼續(xù)執(zhí)行下去。
于是,所有任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)。
同步任務(wù)
在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù);
異步任務(wù)指
不進入主線程、而進入"任務(wù)隊列"(task queue)的任務(wù),只有"任務(wù)隊列"通知主線程,某個異步任務(wù)可以執(zhí)行了,該任務(wù)才會進入主線程執(zhí)行。
具體來說,異步執(zhí)行的運行機制如下。(同步執(zhí)行也是如此,因為它可以被視為沒有異步任務(wù)的異步執(zhí)行。)
(1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧。
(2)主線程之外,還存在一個"任務(wù)隊列"(task queue)。只要異步任務(wù)有了運行結(jié)果,就在"任務(wù)隊列"之中放置一個事件。
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會讀取"任務(wù)隊列",看看里面有哪些事件。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進入執(zhí)行棧,開始執(zhí)行。
(4)主線程不斷重復(fù)上面的第三步。
那怎么知道主線程執(zhí)行棧為空???JavaScript引擎存在monitoring process進程,會持續(xù)不斷的檢查 主線程 執(zhí)行棧是否為空,一旦為空,就會去event queue那里檢查是否有等待被調(diào)用的函數(shù)。

javascript的宏任務(wù)和微任務(wù)
宏任務(wù)有Event Table(事件列表)、Event Queue(事件隊列),微任務(wù)有Event Queue(事件隊列)
1.宏任務(wù):包括整體代碼script,setTimeout,setInterval、I/O、UI 交互事件、setImmediate(Node.js 環(huán)境);
2.微任務(wù):Promise、MutaionObserver、process.nextTick(Node.js 環(huán)境)

setTimeout(function() {
console.log('宏任務(wù)setTimeout'); //先遇到setTimeout,將其回調(diào)函數(shù)注冊后分發(fā)到宏任務(wù)Event Queue
//如果setTimeout設(shè)置時間,那它會先把函數(shù)放到宏任務(wù)Event Table,等時間到了再放入宏任務(wù)Event Queue里面
})
new Promise(function(resolve) {
console.log('微任務(wù)promise'); //new Promise函數(shù)立即執(zhí)行
resolve(); //必須resolve執(zhí)行才能執(zhí)行then
}).then(function() {
console.log('微任務(wù)then'); //then函數(shù)分發(fā)到微任務(wù)Event Queue
})
function test() {
console.log('函數(shù)聲明');
}
text()
console.log('主線程console');
//執(zhí)行順序結(jié)果: 微任務(wù)promise、函數(shù)聲明、主線程console、微任務(wù)then、宏任務(wù)setTimeout
我自己看到這還是不太理解執(zhí)行機制,后來消化了一下
有個問題promise不是微任務(wù)嗎為啥會先輸出“微任務(wù)promise”呢?
new promise 是聲明了promise嗎? 我就聲明了一個test函數(shù)并調(diào)用,驗證下個人想法。
果然輸出了“函數(shù)聲明”,這就是說 在new promise 時 聲明并調(diào)用了promise 所以會輸出“微任務(wù)promise”
上面說到JavaScript是單線程,單線程就意味著,所有任務(wù)需要排隊,也就是上面說到的任務(wù)隊列。
所以我個人這樣理解(個人理解,有錯希望評論指正 圖用processOn制作):
第一次整體代碼script(這時它是一個任務(wù)去遍歷執(zhí)行)>>碰到setTimeout(他是一個宏任務(wù)提出隊列,讓它自己變成一個隊伍,等待script執(zhí)行完畢)>>new Promise(promise的機制會立即執(zhí)行,輸出'微任務(wù)promis')>>.then(放入微任務(wù)隊列)>> test()>>console.log(輸出)>>.then(執(zhí)行微任務(wù)隊列,這里第一次宏任務(wù)執(zhí)行完畢)。
執(zhí)行下一個宏任務(wù)setTimeout>>console.log('宏任務(wù)setTimeout')>>沒有微任務(wù)結(jié)束
