一、JavaScript為什么設(shè)計為單線程?
JavaScript語言的一大特點就是單線程,換言之就是同一個時間只能做一件事。
for(var j = 0; j < 5; j++) { console.log(j); } console.log('end');
上面的代碼,只有for循環(huán)執(zhí)行完畢,才會執(zhí)行end;
JavaScript的單線程,與它的用途有關(guān)。作為瀏覽器腳本語言,JavaScript的主要用途就是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復(fù)雜的同步問題。
假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容,另一個線程刪除了這個節(jié)點,這時瀏覽器應(yīng)該以哪個線程為準?
所以,為了避免復(fù)雜性,從一誕生,JavaScript就是單線程,這已經(jīng)成了這門語言的核心特征,將來也不會改變。
為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質(zhì)。
二、任務(wù)隊列
“任務(wù)隊列”是一個事件的隊列(也可以理解成消息的隊列),IO設(shè)備完成一項任務(wù),就在”任務(wù)隊列”中添加一個事件,表示相關(guān)的異步任務(wù)可以進入”執(zhí)行棧”了。主線程讀取”任務(wù)隊列”,就是讀取里面有哪些事件;
我們來解釋一下:
Javascript是單線程的,單線程就意味著所有任務(wù)需要排隊,前一個任務(wù)結(jié)束,才會執(zhí)行后一個任務(wù)。如果前一個任務(wù)耗時很長,后一個任務(wù)就不得不一直等著。
如果排隊是因為計算量太大,CPU占用高倒也情有可原,但是很多時候CPU是處于空閑狀態(tài)或者有很大的空間未被使用,因為IO設(shè)備(輸入輸出設(shè)備)很慢(比如Ajax操作從網(wǎng)絡(luò)讀取數(shù)據(jù)),只能等結(jié)果出來,才可以往下執(zhí)行。
JavaScript語言的設(shè)計者意識到,主線程完全可以不管IO設(shè)備,把這些等待中的任務(wù)掛起,先運行排在后面的任務(wù)。等到IO設(shè)備返回了結(jié)果,再去執(zhí)行掛起的任務(wù)。
因此,所有任務(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í)行。)
所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧
主線程之外,還存在一個”任務(wù)隊列”(task queue)。只要異步任務(wù)有了運行結(jié)果,就在”任務(wù)隊列”之中放置一個事件。
一旦”執(zhí)行棧”中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會讀取”任務(wù)隊列”,看看里面有哪些事件。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進入執(zhí)行棧,開始執(zhí)行。
主線程不斷重復(fù)執(zhí)行上面的第三步。
for(var j = 0; j < 5; j++) { console.log(j); setTimeout(function() { console.log('timeout'); }, 0); } console.log('end');
//結(jié)果0 1 2 3 4 end timeout timeout timeout timeout timeout