1.個(gè)人覺得沙廉,js中,最基礎(chǔ)的異步是setTimeout和setInterval函數(shù)臼节,很常見撬陵,但是很少人有人知道其實(shí)這就是異步,因?yàn)樗鼈兛梢钥刂苆s的執(zhí)行順序
<pre class="syntaxbox" style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; color: rgb(51, 51, 51); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">var timeoutID = window.setTimeout(func[, delay, param1, param2, ...]);</pre>
看看下面的測試代碼會出現(xiàn)什么网缝?巨税? 要求出現(xiàn)的順序是張一
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> <script type="text/javascript"> console.log( "張一" );
setTimeout(function() {
console.log( "王三" )
}, 500 );
setTimeout(function() {
console.log( "李四" )
}, 500 );
setTimeout(function() {
console.log( "劉五" )
}, 500 );
console.log( "李二" ); </script></pre>
](javascript:void(0); "復(fù)制代碼")
結(jié)果正如設(shè)計(jì)的:
但是 查看 火狐的api文檔 有這樣一句話 api地址 https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout
Because even though setTimeout
was called with a delay of zero, it's placed on a queue and scheduled to run at the next opportunity, not immediately. Currently executing code must complete before functions on the queue are executed, the resulting execution order may not be as expected.
翻譯一下 就是 因?yàn)榧词箂etTimeout調(diào)用延遲為零,這是放置在一個(gè)隊(duì)列和調(diào)度運(yùn)行到下一個(gè)機(jī)會途凫,沒有立即垢夹。當(dāng)前正在執(zhí)行的代碼必須在隊(duì)列上的函數(shù)執(zhí)行之前完成溢吻,由此產(chǎn)生的執(zhí)行順序可能不會像預(yù)期的那樣维费。
所以果元,可預(yù)料的結(jié)果是:直到在同一程序段中所有其余的代碼執(zhí)行結(jié)束后,超時(shí)才會發(fā)生犀盟。所以如果設(shè)置了超時(shí)而晒,同時(shí)執(zhí)行了需長時(shí)間運(yùn)行的函數(shù),那么在該函數(shù)執(zhí)行完成之前阅畴,超時(shí)甚至都不會啟動倡怎。實(shí)際上,異步函數(shù)贱枣,如setTimeout和setInterval监署,被壓入了稱之為Event Loop的隊(duì)列。有點(diǎn)扯遠(yuǎn)纽哥,記住就是setTimeout和setInterval可以改變一個(gè)隊(duì)列函數(shù)的執(zhí)行順序钠乏。
2.ajax異步
很多時(shí)候,我們使用的ajax都是jQuery封裝后的.ajax() 如:api中的ajax格式春塌。
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> $.ajax({
type: "POST",
url: "some.php",
data: "name=John&location=Boston",
success: function(msg){
alert( "Data Saved: " + msg );
}
});</pre>
](javascript:void(0); "復(fù)制代碼")
很多人的理解是 是在調(diào)用$.ajax之后馬上使用data晓避? 但是真的是這樣嗎?
如果你寫過原生的ajax只壳,應(yīng)該會知道如下:https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Types_of_requests
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">function reqListener () {
console.log(this.responseText);
} var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send();</pre>
](javascript:void(0); "復(fù)制代碼")
這個(gè)火狐官方的例子很好的說明我們的ajax執(zhí)行的步驟俏拱,XmlHttpRequest對象發(fā)起請求,設(shè)置回調(diào)函數(shù)用來處理XHR的readystatechnage事件吼句。然后執(zhí)行XHR的send方法锅必。在XHR運(yùn)行中,當(dāng)其屬性readyState改變時(shí)readystatechange事件就會被觸發(fā)惕艳,只有在XHR從遠(yuǎn)端服務(wù)器接收響應(yīng)結(jié)束時(shí)回調(diào)函數(shù)才會觸發(fā)執(zhí)行况毅。也就是如下:
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">xmlhttp.open( "GET", "url", true );
xmlhttp.onreadystatechange = function( data ) { if ( xmlhttp.readyState === 4 ) {
console.log( data );
}
};
xmlhttp.send( null );</pre>
](javascript:void(0); "復(fù)制代碼")
以上兩點(diǎn)就是JavaScript中的同步和異步。
延伸 摘自阮一峰e(cuò)s6入門:
于是尔艇,這個(gè)問題又回到了最開始的起點(diǎn):JavaScript是單線程的尔许。
JavaScript語言的一大特點(diǎn)就是單線程,也就是說终娃,同一個(gè)時(shí)間只能做一件事味廊。那么,為什么JavaScript不能有多個(gè)線程呢棠耕?這樣能提高效率啊余佛。
JavaScript的單線程,與它的用途有關(guān)窍荧。作為瀏覽器腳本語言辉巡,JavaScript的主要用途是與用戶互動,以及操作DOM蕊退。這決定了它只能是單線程郊楣,否則會帶來很復(fù)雜的同步問題憔恳。比如,假定JavaScript同時(shí)有兩個(gè)線程净蚤,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容钥组,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)今瀑?
所以程梦,為了避免復(fù)雜性,從一誕生橘荠,JavaScript就是單線程屿附,這已經(jīng)成了這門語言的核心特征,將來也不會改變哥童。
為了利用多核CPU的計(jì)算能力拿撩,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程如蚜,但是子線程完全受主線程控制压恒,且不得操作DOM。所以错邦,這個(gè)新標(biāo)準(zhǔn)并沒有改變JavaScript單線程的本質(zhì)探赫。
單線程就意味著,所有任務(wù)需要排隊(duì)撬呢,前一個(gè)任務(wù)結(jié)束伦吠,才會執(zhí)行后一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長魂拦,后一個(gè)任務(wù)就不得不一直等著毛仪。于是就有一個(gè)概念,任務(wù)隊(duì)列芯勘。
如果排隊(duì)是因?yàn)橛?jì)算量大箱靴,CPU忙不過來,倒也算了荷愕,但是很多時(shí)候CPU是閑著的衡怀,因?yàn)镮O設(shè)備(輸入輸出設(shè)備)很慢(比如Ajax操作從網(wǎng)絡(luò)讀取數(shù)據(jù)),不得不等著結(jié)果出來安疗,再往下執(zhí)行抛杨。
JavaScript語言的設(shè)計(jì)者意識到,這時(shí)主線程完全可以不管IO設(shè)備荐类,掛起處于等待中的任務(wù)怖现,先運(yùn)行排在后面的任務(wù)。等到IO設(shè)備返回了結(jié)果玉罐,再回過頭屈嗤,把掛起的任務(wù)繼續(xù)執(zhí)行下去潘拨。
于是,所有任務(wù)可以分成兩種恢共,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)璧亚。同步任務(wù)指的是讨韭,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢癣蟋,才能執(zhí)行后一個(gè)任務(wù)透硝;異步任務(wù)指的是,不進(jìn)入主線程疯搅、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù)濒生,只有"任務(wù)隊(duì)列"通知主線程甜橱,某個(gè)異步任務(wù)可以執(zhí)行了润绎,該任務(wù)才會進(jìn)入主線程執(zhí)行。
具體來說蜕衡,異步執(zhí)行的運(yùn)行機(jī)制如下礁蔗。(同步執(zhí)行也是如此觉义,因?yàn)樗梢员灰暈闆]有異步任務(wù)的異步執(zhí)行。)
(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)就會讀取"任務(wù)隊(duì)列"瘤缩,看看里面有哪些事件。那些對應(yīng)的異步任務(wù)伦泥,于是結(jié)束等待狀態(tài)款咖,進(jìn)入執(zhí)行棧,開始執(zhí)行奄喂。
(4)主線程不斷重復(fù)上面的第三步铐殃。
下圖就是主線程和任務(wù)隊(duì)列的示意圖。
只要主線程空了跨新,就會去讀取"任務(wù)隊(duì)列"富腊,這就是JavaScript的運(yùn)行機(jī)制。這個(gè)過程會不斷重復(fù)域帐。
"任務(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)擊、頁面滾動等等)野舶。只要指定過回調(diào)函數(shù)易迹,這些事件發(fā)生時(shí)就會進(jìn)入"任務(wù)隊(duì)列",等待主線程讀取平道。
所謂"回調(diào)函數(shù)"(callback)睹欲,就是那些會被主線程掛起來的代碼。異步任務(wù)必須指定回調(diào)函數(shù)一屋,當(dāng)主線程開始執(zhí)行異步任務(wù)窘疮,就是執(zhí)行對應(yīng)的回調(diào)函數(shù)。
"任務(wù)隊(duì)列"是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)冀墨,排在前面的事件考余,優(yōu)先被主線程讀取。主線程的讀取過程基本上是自動的轧苫,只要執(zhí)行棧一清空楚堤,"任務(wù)隊(duì)列"上第一位的事件就自動進(jìn)入主線程。但是含懊,由于存在后文提到的"定時(shí)器"功能身冬,主線程首先要檢查一下執(zhí)行時(shí)間,某些事件只有到了規(guī)定的時(shí)間岔乔,才能返回主線程酥筝。