一、單線程
- 主線程:JavaScript是單線程的,所謂單線程,是指在JS引擎中負(fù)責(zé)解釋和執(zhí)行JavaScript代碼的線程只有一個(gè),叫它主線程;
- 工作線程:實(shí)際上瀏覽器還存在其他的線程,例如:處理AJAX請求的線程、處理DOM事件的線程、定時(shí)器線程、讀寫文件的線程(例如在Node.js中)等等,這些線程可能存在于JS引擎之內(nèi),也可能存在于JS引擎之外,在此我們不作區(qū)分,統(tǒng)一叫它們工作線程;
- 總結(jié):
?① JavaScript引擎是單線程運(yùn)行的,瀏覽器無論在什么時(shí)候都有且只有一個(gè)線程在運(yùn)行JavaScript程序;
?② JavaScript引擎用單線程運(yùn)行也是有意義的,單線程不必理會(huì)線程同步這些復(fù)雜的問題,問題得到簡化;
二、同步和異步、阻塞和非阻塞
- 區(qū)別:在于程序中的各個(gè)任務(wù)是否按順序執(zhí)行,異步操作可以改變程序的正常執(zhí)行順序;
console.log("1");
setTimeout(function() {
console.log("2")
}, 0);
setTimeout(function() {
console.log("3")
}, 0);
setTimeout(function() {
console.log("4")
}, 0);
console.log("5");
// 1
// 5
// 2
// 3
// 4
什么是異步任務(wù)?上述代碼中,盡管setTimeout的time延遲時(shí)間為0,其中的function也會(huì)被放入任務(wù)隊(duì)列中等待下一個(gè)機(jī)會(huì)到來時(shí)執(zhí)行,而不需要加入任務(wù)隊(duì)列中的程序必須在任務(wù)隊(duì)列的程序之前完成,因此程序的執(zhí)行順序可能和代碼中的順序不一致;
任務(wù)隊(duì)列的執(zhí)行時(shí)機(jī):任務(wù)隊(duì)列中的回調(diào)函數(shù)必須等待任務(wù)隊(duì)列之外的所有代碼執(zhí)行完畢之后再執(zhí)行,這是因?yàn)閳?zhí)行程序的時(shí)候,瀏覽器默認(rèn)setTimeout以及ajax請求這一類的方法為耗時(shí)程序(盡管有時(shí)候并不耗時(shí)),將其加入一個(gè)隊(duì)列,該隊(duì)列是一個(gè)存儲(chǔ)耗時(shí)程序的隊(duì)列,在所有不耗時(shí)程序執(zhí)行完后,再來依次執(zhí)行任務(wù)隊(duì)列中的程序;
任務(wù)排隊(duì):因?yàn)閖avascript是單線程的,這意味著所有的任務(wù)需要排隊(duì)處理,前一個(gè)任務(wù)結(jié)束,才會(huì)執(zhí)行后一個(gè)任務(wù),如果前一個(gè)任務(wù)耗時(shí)很長,后一個(gè)任務(wù)就不得不一直等著,于是就有了任務(wù)隊(duì)列這個(gè)概念;如果排隊(duì)是因?yàn)橛?jì)算量大,CPU忙不過來倒也還好,很多時(shí)候CPU是閑著的,因?yàn)镮O設(shè)備很慢(比如AJAX操作從網(wǎng)絡(luò)讀取數(shù)據(jù)),不得不等著結(jié)果出來,再往下執(zhí)行,于是JS語言的設(shè)計(jì)者意識(shí)到,這時(shí)主線程完全可以不管IO設(shè)備,掛起處于等待中的任務(wù),先運(yùn)行排在后面的任務(wù),等到IO設(shè)備返回了結(jié)果,再回來把掛起的任務(wù)繼續(xù)執(zhí)行下去;
兩種任務(wù):一種是同步任務(wù)(synchronous),是指在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù);二是異步任務(wù)(asynchronous):是指不進(jìn)入主線程、而進(jìn)入“任務(wù)隊(duì)列”(task queue)的任務(wù),只有等主線程任務(wù)執(zhí)行完畢,任務(wù)隊(duì)列才開始通知主線程請求執(zhí)行任務(wù),該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行;
具體的異步運(yùn)行機(jī)制:
1)所有的同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧(execution context stack);
2)主線程之外,還存在一個(gè)“任務(wù)隊(duì)列”(task queue),只要異步任務(wù)有了運(yùn)行結(jié)果,就在“任務(wù)隊(duì)列”之中放置一個(gè)事件;
3)一旦“執(zhí)行?!敝械乃型饺蝿?wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取“任務(wù)隊(duì)列”,看看里面有哪些事件,那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),開始執(zhí)行;
4)主線程不斷重復(fù)上面的第三步進(jìn)行事件循環(huán),只要主線程空了,就會(huì)去讀取“任務(wù)隊(duì)列”,這就是JS的運(yùn)行機(jī)制,這個(gè)過程會(huì)不斷重復(fù);任務(wù)隊(duì)列中的事件:任務(wù)隊(duì)列是一個(gè)事件的隊(duì)列,也可以理解成消息的隊(duì)列,IO設(shè)備完成一項(xiàng)任務(wù),就在任務(wù)隊(duì)列中添加一個(gè)事件,表示相關(guān)的異步任務(wù)可以進(jìn)入“執(zhí)行?!绷?,主線程讀取“任務(wù)隊(duì)列”,就是讀里面有哪些事件;任務(wù)隊(duì)列中的事件,除了IO設(shè)備的事件以外,還包括一些用戶產(chǎn)生的事件(比如鼠標(biāo)點(diǎn)擊、頁面滾動(dòng)等),比如$(selectot).click(function),這些都是相對耗時(shí)的操作,只要指定過這些事件的回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入任務(wù)隊(duì)列等待主線程讀取;
回調(diào)函數(shù)(callback):就是那些會(huì)被主線程掛起來的代碼,前面所說的點(diǎn)擊事件$(selectot).click(function)中的function就是一個(gè)回調(diào)函數(shù),異步任務(wù)必須指定回調(diào)函數(shù),當(dāng)主線程開始執(zhí)行異步任務(wù),就是執(zhí)行對應(yīng)的回調(diào)函數(shù),例如ajax的success,complete,error也都指定了各自的回調(diào)函數(shù),這些函數(shù)就會(huì)加入任務(wù)隊(duì)列中,等待執(zhí)行;
下面以AJAX請求為例,來看一下同步和異步的區(qū)別:
異步AJAX:
主線程:“你好,AJAX線程。請你幫我發(fā)個(gè)HTTP請求吧,我把請求地址和參數(shù)都給你了?!?br> AJAX線程:“好的,主線程。我馬上去發(fā),但可能要花點(diǎn)兒時(shí)間呢,你可以先去忙別的?!?br> 主線程::“謝謝,你拿到響應(yīng)后告訴我一聲啊。”
(接著,主線程做其他事情去了。一頓飯的時(shí)間后,它收到了響應(yīng)到達(dá)的通知。)
同步AJAX:
主線程:“你好,AJAX線程。請你幫我發(fā)個(gè)HTTP請求吧,我把請求地址和參數(shù)都給你了。”
AJAX線程:“......”
主線程::“喂,AJAX線程,你怎么不說話?”
AJAX線程:“......”
主線程::“喂!喂喂喂!”
AJAX線程:“......”
(一炷香的時(shí)間后)
主線程::“喂!求你說句話吧!”
AJAX線程:“主線程,不好意思,我在工作的時(shí)候不能說話。你的請求已經(jīng)發(fā)完了,拿到響應(yīng)數(shù)據(jù)了,給你。”
??????正是由于JavaScript是單線程的,而異步容易實(shí)現(xiàn)非阻塞,所以在JavaScript中對于耗時(shí)的操作或者時(shí)間不確定的操作,使用異步就成了必然的選擇;
- 同步阻塞案例:
// 這是一個(gè)阻塞式函數(shù), 將一個(gè)文件復(fù)制到另一個(gè)文件上
// 調(diào)用這個(gè)”copyBigFile()”函數(shù),將一個(gè)大文件復(fù)制到另一個(gè)文件上,將耗時(shí)1小時(shí),意味著這個(gè)函數(shù)的將在一個(gè)小時(shí)之后返回
function copyBigFile(afile, bfile){
var result = copyFileSync(afile,bfile);
return result;
}
//這是一段程序
console.log("start copying ... ");
var a = copyBigFile('A.txt', 'B.txt'); //這行程序?qū)⒑臅r(shí)1小時(shí)
console.log("Finished"); // 這行程序?qū)⒃谝恍r(shí)后執(zhí)行
console.log("處理一下別的事情"); // 這行程序?qū)⒃谝恍r(shí)后執(zhí)行
console.log("Hello World, 整個(gè)程序已加載完畢,請享用"); // 這行程序?qū)⒃谝恍r(shí)后執(zhí)行
- 同步非阻塞案例:
// 這是一個(gè)非阻塞式函數(shù)
// 如果復(fù)制已完成,則返回 true, 如果未完成則返回 false
// 調(diào)用這個(gè)函數(shù)將立刻返回結(jié)果
function copyBigFile(afile,bfile){
var copying = copyFileAsync(afile, bfile);
var isFinished = !copying;
return !isFinished;
}
console.log("start copying ... ");
// 同步的程序需要在一個(gè)循環(huán)中輪詢結(jié)果
while( a = copyBigFile('A.txt', 'B.txt')){
console.log("在這之間還可以處理別的事情");
} ;
console.log("Finished"); // 這行程序?qū)⒃谝恍r(shí)后執(zhí)行
console.log("Hello World, 整個(gè)程序已加載完畢,請享用"); // 這行程序?qū)⒃谝恍r(shí)后執(zhí)行
// 非阻塞式的函數(shù)給編程帶來了更多的便利,在長IO操作的同時(shí),可以寫點(diǎn)其他的程序,提高效率,執(zhí)行結(jié)果如下:
// start copying ...
// 在這之間還可以處理別的事情
// 在這之間還可以處理別的事情
// 在這之間還可以處理別的事情
// ...
// Finished
// Hello World, 整個(gè)程序已加載完畢,請享用
-
異步非阻塞案例:
??????同步的程序需要在一個(gè)循環(huán)中輪詢結(jié)果,循環(huán)里面的程序會(huì)被執(zhí)行好多遍,所以并不好控制來寫一些正常的程序,很難再利用起來,更為合理的方式是對非阻塞式的函數(shù)進(jìn)行利用,也就是主線程不會(huì)主動(dòng)地去詢問結(jié)果,而是當(dāng)任務(wù)有了結(jié)果的時(shí)候再來通知主線程;
//非阻塞式的有異步通知能力的函數(shù)
//以下不需要看懂,只用知道這個(gè)函數(shù)會(huì)在完成copy操作之后,執(zhí)行success
function copyBigFile(afile,bfile, callback){
var copying = copyFileAsync(afile, bfile, function(){ callback();});
var isFinished = !copying;
return !isFinished;
}
// 不同于上一個(gè)同步非阻塞函數(shù)的地方在于它具有通知功能,能夠在完成操作之后主動(dòng)地通知程序,“我完成了”
console.log("start copying ... ");
copyBigFile("A.txt","B.txt", function(){
console.log("Finished"); //一個(gè)小時(shí)后被執(zhí)行
console.log("Hello World, 整個(gè)程序已加載完畢,請享用"); //一個(gè)小時(shí)后被執(zhí)行
})
console.log("干別的事情");
console.log("做一些別的處理");
// 程序在調(diào)用copyBigFile函數(shù)之后,可以立即獲得返回值,線程沒有被阻塞住,于是還可以去干些別的事情,然后當(dāng)copyBigFile完成之后,會(huì)執(zhí)行指定的函數(shù)
// start copying ...
// 干別的事情
// 做一些別的處理
// Finished
// Hello World, 整個(gè)程序已加載完畢,請享用
三、異步過程的構(gòu)成要素
??????從上文可以看出,異步函數(shù)實(shí)際上很快就調(diào)用完成了,但是后面還有工作線程執(zhí)行異步任務(wù)、通知主線程、主線程調(diào)用回調(diào)函數(shù)等很多步驟,我們把整個(gè)過程叫做異步過程,異步函數(shù)的調(diào)用在整個(gè)異步過程中,只是一小部分;
??????一個(gè)異步過程通常是這樣的:主線程發(fā)起一個(gè)異步請求,相應(yīng)的工作線程接收請求并告知主線程已收到(異步函數(shù)返回);主線程可以繼續(xù)執(zhí)行后面的代碼,同時(shí)工作線程執(zhí)行異步任務(wù);工作線程完成工作后,通知主線程;主線程收到通知后,執(zhí)行一定的動(dòng)作(調(diào)用回調(diào)函數(shù));
??????異步調(diào)用一般分為兩個(gè)階段,提交請求和處理結(jié)果,這兩個(gè)階段之間有事件循環(huán)的調(diào)用,它們屬于兩個(gè)不同的事件循環(huán)(tick),彼此沒有關(guān)聯(lián),異步調(diào)用一般以傳入callback的方式來指定異步操作完成后要執(zhí)行的動(dòng)作,而異步調(diào)用本體和callback屬于不同的事件循環(huán);
try/catch語句只能捕獲當(dāng)次事件循環(huán)的異常,對callback無能為力
// 異步函數(shù)通常具有以下的形式:
A(args...,callbackFn)
// 它可以叫做異步過程的發(fā)起函數(shù),或者叫做異步任務(wù)注冊函數(shù),args是這個(gè)函數(shù)需要的參數(shù),callbackFn也是這個(gè)函數(shù)的參數(shù),但是它比較特殊所以單獨(dú)列出來;
從主線程的角度看,一個(gè)異步過程包括下面兩個(gè)要素:
1)發(fā)起函數(shù)(或叫注冊函數(shù))A提交請求
2)回調(diào)函數(shù)callbackFn
它們都是在主線程上調(diào)用的,其中注冊函數(shù)用來發(fā)起異步過程,回調(diào)函數(shù)用來處理結(jié)果;
// 舉個(gè)具體的例子:
setTimeout(fn, 1000);
// 其中的setTimeout就是異步過程的發(fā)起函數(shù),fn是回調(diào)函數(shù)。
// 注意:前面說的形式A(args..., callbackFn)只是一種抽象的表示,并不代表回調(diào)函數(shù)一定要作為發(fā)起函數(shù)的參數(shù),例如:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回調(diào)函數(shù)
xhr.open('GET', url);
xhr.send(); // 發(fā)起函數(shù)
// 發(fā)起函數(shù)和回調(diào)函數(shù)就是分離的。
四、消息隊(duì)列和事件循環(huán)
JS是單線程的,但卻能執(zhí)行異步任務(wù),這主要是因?yàn)镴S中存在事件循環(huán)(Event Loop)和任務(wù)隊(duì)列(Task Queue);
事件循環(huán):JS會(huì)創(chuàng)建一個(gè)類似于while(true)的循環(huán),每執(zhí)行一次循環(huán)體的過程稱為Tick,每次Tick的過程就是查看是否有待處理事件,如果有則取出相關(guān)事件及回調(diào)函數(shù)放入執(zhí)行棧中由主線程執(zhí)行,待處理的事件會(huì)存儲(chǔ)在一個(gè)任務(wù)隊(duì)列中,也就是每次Tick都會(huì)查看任務(wù)隊(duì)列中是否有需要執(zhí)行的任務(wù);實(shí)際上事件循環(huán)是指主線程重復(fù)從消息隊(duì)列取出消息、執(zhí)行的過程;
任務(wù)隊(duì)列:也稱為消息隊(duì)列,是一個(gè)先進(jìn)先出的隊(duì)列,它里面存放著各種消息,即異步操作的回調(diào)函數(shù),異步操作會(huì)將相關(guān)回調(diào)添加到任務(wù)隊(duì)列中,而不同的異步操作添加到任務(wù)隊(duì)列的時(shí)機(jī)也不同,如onclick,setTimeout,ajax處理的方式都不同,這些異步操作都是由瀏覽器內(nèi)核的不同模塊來執(zhí)行的:
1)onclick由瀏覽器內(nèi)核的DOM Binding模塊來處理,當(dāng)事件觸發(fā)的時(shí)候,回調(diào)函數(shù)會(huì)立即添加到任務(wù)隊(duì)列中;
2)setTimeout會(huì)由瀏覽器內(nèi)核的timer模塊來進(jìn)行延時(shí)處理,當(dāng)時(shí)間到達(dá)的時(shí)候,才會(huì)將回調(diào)函數(shù)添加到任務(wù)隊(duì)列中;
3)ajax會(huì)由瀏覽器內(nèi)核的network模塊來處理,在網(wǎng)絡(luò)請求完成返回之后,才將回調(diào)添加到任務(wù)隊(duì)列中;主線程:JS只有一個(gè)線程,稱之為主線程,而事件循環(huán)是主線程中執(zhí)行棧里的代碼執(zhí)行完畢之后,才開始執(zhí)行的,因此主線程中要執(zhí)行的代碼時(shí)間過長,會(huì)阻塞事件循環(huán)的執(zhí)行,也就會(huì)阻塞異步操作的執(zhí)行,只有當(dāng)主線程中執(zhí)行棧為空的時(shí)候,即同步代碼執(zhí)行完后,才會(huì)進(jìn)行事件循環(huán)來觀察要執(zhí)行的事件回調(diào),當(dāng)事件循環(huán)檢測到任務(wù)隊(duì)列中有事件就取出相關(guān)回調(diào)放入執(zhí)行棧中由主線程執(zhí)行;
ES6 新增的任務(wù)隊(duì)列:ES6 中新增的任務(wù)隊(duì)列是在事件循環(huán)之上的,事件循環(huán)每次 tick 后會(huì)查看 ES6 的任務(wù)隊(duì)列中是否有任務(wù)要執(zhí)行,也就是 ES6 的任務(wù)隊(duì)列比事件循環(huán)中的任務(wù)隊(duì)列優(yōu)先級(jí)更高,如 Promise 就使用了 ES6 的任務(wù)隊(duì)列特性;
AJAX異步:JS是單線程運(yùn)行的,XMLHttpRequest在連接后是異步的,請求是由瀏覽器新開一個(gè)線程請求的,當(dāng)請求的狀態(tài)變更時(shí),如果先前已設(shè)置回調(diào),這異步線程就產(chǎn)生狀態(tài)變更事件放到JS引擎的處理隊(duì)列中等待處理,當(dāng)任務(wù)被處理時(shí),JS引擎始終是單線程運(yùn)行回調(diào)函數(shù),即onreadystatechange所設(shè)置的函數(shù);
異步過程中,工作線程在異步操作完成后需要通知主線程,那么這個(gè)通知機(jī)制是怎樣實(shí)現(xiàn)的呢?答案是利用消息隊(duì)列和事件循環(huán);
工作線程將消息放到消息隊(duì)列,主線程通過事件循環(huán)過程去取消息;
實(shí)際上,主線程只會(huì)做一件事情,就是從消息隊(duì)列里面取消息、執(zhí)行消息,再取消息、再執(zhí)行,當(dāng)消息隊(duì)列為空時(shí),就會(huì)等待直到消息隊(duì)列變成非空,而且主線程只有在將當(dāng)前的消息執(zhí)行完成后,才會(huì)去取下一個(gè)消息,這種機(jī)制就叫做事件循環(huán)機(jī)制,取一個(gè)消息并執(zhí)行的過程叫做一次循環(huán);
// 事件循環(huán)用代碼表示大概是這樣的:
while(true) {
var message = queue.get();
execute(message);
}
- 那么消息隊(duì)列中放的消息具體是什么東西呢?消息的具體結(jié)構(gòu)當(dāng)然跟具體的實(shí)現(xiàn)有關(guān),但是為了簡單起見,我們可以認(rèn)為:消息就是注冊異步任務(wù)時(shí)添加的回調(diào)函數(shù);
// 再次以異步AJAX為例,假設(shè)存在如下的代碼:
$.ajax('http://segmentfault.com', function(resp) {
console.log('我是響應(yīng):', resp);
});
// 其他代碼
...
...
...
- 主線程在發(fā)起AJAX請求后,會(huì)繼續(xù)執(zhí)行其他代碼,AJAX線程負(fù)責(zé)請求segmentfault.com,拿到響應(yīng)后,它會(huì)把響應(yīng)封裝成一個(gè)JavaScript對象,然后構(gòu)造一條消息:
// 消息隊(duì)列中的消息就長這個(gè)樣子
var message = function () {
callbackFn(response);
}
-
其中的callbackFn就是前面代碼中得到成功響應(yīng)時(shí)的回調(diào)函數(shù),主線程在執(zhí)行完當(dāng)前循環(huán)中的所有代碼后,就會(huì)到消息隊(duì)列取出這條消息(也就是message函數(shù)),并執(zhí)行它,到此為止,就完成了工作線程對主線程的通知,回調(diào)函數(shù)也就得到了執(zhí)行,如果一開始主線程就沒有提供回調(diào)函數(shù),AJAX線程在收到HTTP響應(yīng)后,也就沒必要通知主線程,從而也沒必要往消息隊(duì)列放消息,用圖表示這個(gè)過程就是:
屏幕快照 2018-08-20 下午6.57.24.png - 異步過程的回調(diào)函數(shù),一定不在當(dāng)前這一輪事件循環(huán)中執(zhí)行;
- 還有一點(diǎn)需要注意的是:觸發(fā)和執(zhí)行并不是同一概念,計(jì)時(shí)器的回調(diào)函數(shù)一定會(huì)在指定delay的時(shí)間后被觸發(fā),但并不一定立即執(zhí)行,可能需要等待,所有的js代碼都是在同一個(gè)線程里執(zhí)行的,但像鼠標(biāo)點(diǎn)擊和計(jì)時(shí)器之類的事件只有在js單線程空閑時(shí)才執(zhí)行;
五、異步與事件
- 上文中所說的“事件循環(huán)”,為什么里面有個(gè)事件呢?那是因?yàn)椋合㈥?duì)列中的每條消息實(shí)際上都對應(yīng)著一個(gè)事件;
- 上文中一直沒有提到一類很重要的異步過程:DOM事件;
// 舉例
var button = document.getElement('#btn');
button.addEventListener('click', function(e) {
console.log();
});
- 從事件的角度來看,上述代碼表示:在按鈕上添加了一個(gè)鼠標(biāo)單擊事件的事件監(jiān)聽器,當(dāng)用戶點(diǎn)擊按鈕時(shí),鼠標(biāo)單擊事件觸發(fā),事件監(jiān)聽器函數(shù)被調(diào)用;
- 從異步過程的角度看,addEventListener函數(shù)就是異步過程的發(fā)起函數(shù),事件監(jiān)聽器函數(shù)就是異步過程的回調(diào)函數(shù),事件觸發(fā)時(shí),表示異步任務(wù)完成,會(huì)將事件監(jiān)聽器函數(shù)封裝成一條消息放到消息隊(duì)列中,等待主線程執(zhí)行;
- 事件的概念實(shí)際上并不是必須的,事件機(jī)制實(shí)際上就是異步過程的通知機(jī)制,我覺得它的存在是為了編程接口對開發(fā)者更友好
- 另一方面,所有的異步過程也都可以用事件來描述,例如:setTimeout可以看成對應(yīng)一個(gè)時(shí)間到了的事件,前文的setTimeout(fn,1000);可以看成:
timer.addEventListener('timeout', 1000, fn);
六、生產(chǎn)者與消費(fèi)者
- 從生產(chǎn)者與消費(fèi)者的角度看,異步過程是這樣的:
- 工作線程是生產(chǎn)者,主線程是消費(fèi)者(只有一個(gè)消費(fèi)者)。工作線程執(zhí)行異步任務(wù),執(zhí)行完成后把對應(yīng)的回調(diào)函數(shù)封裝成一條消息放到消息隊(duì)列中;主線程不斷地從消息隊(duì)列中取消息并執(zhí)行,當(dāng)消息隊(duì)列空時(shí)主線程阻塞,直到消息隊(duì)列再次非空。
七、總結(jié)
- 最后再用一個(gè)生活中的例子總結(jié)一下同步和異步:在公路上,汽車一輛接一輛,有條不紊的運(yùn)行,這時(shí),有一輛車壞掉了,假如它停在原地進(jìn)行修理,那么后面的車就會(huì)被堵住沒法行駛,交通就亂套了,幸好旁邊有應(yīng)急車道,可以把故障車輛推到應(yīng)急車道修理,而正常的車流不會(huì)受到任何影響。等車修好了,再從應(yīng)急車道回到正常車道即可。唯一的影響就是,應(yīng)急車道用多了,原來的車輛之間的順序會(huì)有點(diǎn)亂;
- 這就是同步和異步的區(qū)別,同步可以保證順序一致,但是容易導(dǎo)致阻塞;異步可以解決阻塞問題,但是會(huì)改變順序性,改變順序性其實(shí)也沒有什么大不了的,只不過讓程序變得稍微難理解了一些;
- PS:ECMAScript 262規(guī)范中,并沒有對異步、事件隊(duì)列等概念及其實(shí)現(xiàn)的描述。這些都是具體的JavaScript運(yùn)行時(shí)環(huán)境使用的機(jī)制。本文重點(diǎn)是描述異步過程的原理,為了便于理解做了很多簡化。所以文中的某些術(shù)語的使用可能是不準(zhǔn)確的,具體細(xì)節(jié)也未必是正確的,例如消息隊(duì)列中消息的結(jié)構(gòu)。請讀者注意。
八、Event Loop的其他解釋
- Event Loop是一個(gè)程序結(jié)構(gòu),用于等待和發(fā)送消息和事件;
-
簡單說,就是在程序中設(shè)置兩個(gè)線程:一個(gè)負(fù)責(zé)程序本身的運(yùn)行,稱為"主線程";另一個(gè)負(fù)責(zé)主線程與其他進(jìn)程(主要是各種I/O操作)的通信,被稱為"Event Loop線程"(可以譯為"消息線程");
Event Loop
??????上圖主線程的綠色部分,還是表示運(yùn)行時(shí)間,而橙色部分表示空閑時(shí)間。每當(dāng)遇到I/O的時(shí)候,主線程就讓Event Loop線程去通知相應(yīng)的I/O程序,然后接著往后運(yùn)行,所以不存在紅色的等待時(shí)間。等到I/O程序完成操作,Event Loop線程再把結(jié)果返回主線程。主線程就調(diào)用事先設(shè)定的回調(diào)函數(shù),完成整個(gè)任務(wù);
??????可以看到,由于多出了橙色的空閑時(shí)間,所以主線程得以運(yùn)行更多的任務(wù),這就提高了效率。這種運(yùn)行方式稱為"異步模式"(asynchronous I/O)或"非堵塞模式"(non-blocking mode);
參考鏈接:
JavaScript:徹底理解同步、異步和事件循環(huán)(Event Loop)
js-關(guān)于異步原理的理解和總結(jié)
js中的同步和異步的個(gè)人理解
JS之異步概念
深入解析Javascript異步編程

