摘要:回調(diào)函數(shù),一般在同步情境下是最后執(zhí)行的,而在異步情境下有可能不執(zhí)行,因?yàn)槭录](méi)有被觸發(fā)或者條件不滿足。同步方式請(qǐng)求異步同步請(qǐng)求當(dāng)請(qǐng)求開始發(fā)送時(shí),瀏覽器事件線程通知主線程,讓線程發(fā)送數(shù)據(jù)請(qǐng)求,主線程收到
一直以來(lái)都知道JavaScript是一門單線程語(yǔ)言,在筆試過(guò)程中不斷的遇到一些輸出結(jié)果的問(wèn)題,考量的是對(duì)異步編程掌握情況。一般被問(wèn)到異步的時(shí)候腦子里第一反應(yīng)就是Ajax,setTimseout...這些東西。在平時(shí)做項(xiàng)目過(guò)程中,基本大多數(shù)操作都是異步的。JavaScript異步都是通過(guò)回調(diào)形式完成的,開發(fā)過(guò)程中一直在處理回調(diào),可能不知不覺(jué)中自己就已經(jīng)處在回調(diào)地獄中。
瀏覽器線程
在開始之前簡(jiǎn)單的說(shuō)一下瀏覽器的線程,對(duì)瀏覽器的作業(yè)有個(gè)基礎(chǔ)的認(rèn)識(shí)。之前說(shuō)過(guò)JavaScript是單線程作業(yè),但是并不代表瀏覽器就是單線程的。
在JavaScript引擎中負(fù)責(zé)解析和執(zhí)行JavaScript代碼的線程只有一個(gè)。但是除了這個(gè)主進(jìn)程以外,還有其他很多輔助線程。那么諸如onclick回調(diào),setTimeout,Ajax這些都是怎么實(shí)現(xiàn)的呢?即瀏覽器搞了幾個(gè)其他線程去輔助JavaScript線程的運(yùn)行。
瀏覽器有很多線程,例如:
GUI渲染線程 - GUI渲染線程處于掛起狀態(tài)的,也就是凍結(jié)狀態(tài)
JavaScript引擎線程 - 用于解析JavaScript代碼
定時(shí)器觸發(fā)線程 - 瀏覽器定時(shí)計(jì)數(shù)器并不是 js引擎計(jì)數(shù)
瀏覽器事件線程 - 用于解析BOM渲染等工作
http線程 - 主要負(fù)責(zé)數(shù)據(jù)請(qǐng)求
EventLoop輪詢處理線程 - 事件被觸發(fā)時(shí)該線程會(huì)把事件添加到待處理隊(duì)列的隊(duì)尾
等等等
從上面來(lái)看可以得出,瀏覽器其實(shí)也做了很多事情,遠(yuǎn)遠(yuǎn)的沒(méi)有想象中的那么簡(jiǎn)單,上面這些線程中GUI渲染線程,JavaScript引擎線程,瀏覽器事件線程是瀏覽器的常駐線程。
當(dāng)瀏覽器開始解析代碼的時(shí)候,會(huì)根據(jù)代碼去分配給不同的輔助線程去作業(yè)。
進(jìn)程
進(jìn)程是指在操作系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
線程
線程是指進(jìn)程內(nèi)獨(dú)立執(zhí)行某個(gè)任務(wù)的一個(gè)單元。線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計(jì)數(shù)器,一組寄存器和棧)。
進(jìn)程中包含線程,一個(gè)進(jìn)程中可以有N個(gè)進(jìn)程。我們可以在電腦的任務(wù)管理器中查看到正在運(yùn)行的進(jìn)程,可以認(rèn)為一個(gè)進(jìn)程就是在運(yùn)行一個(gè)程序,比如用瀏覽器打開一個(gè)網(wǎng)頁(yè),這就是開啟了一個(gè)進(jìn)程。但是比如打開3個(gè)瀏覽器,那么就開啟了3個(gè)進(jìn)程。
同步&異步
既然要了解同步異步當(dāng)然要簡(jiǎn)單的說(shuō)一下同步和異步。說(shuō)到同步和異步最有發(fā)言權(quán)的真的就屬Ajax了,為了讓例子更加明顯沒(méi)有使用Ajax舉例。(●ˇ?ˇ●)
同步
同步會(huì)逐行執(zhí)行代碼,會(huì)對(duì)后續(xù)代碼造成阻塞,直至代碼接收到預(yù)期的結(jié)果之后,才會(huì)繼續(xù)向下執(zhí)行。
console.log(1); alert("同步"); console.log(2); // 結(jié)果: // 1 // 同步 // 2
異步
如果在函數(shù)返回的時(shí)候,調(diào)用者還不能夠得到預(yù)期結(jié)果,而是將來(lái)通過(guò)一定的手段得到結(jié)果(例如回調(diào)函數(shù)),這就是異步。
console.log(1); setTimeout(() => { alert("異步"); },0); console.log(2); // 結(jié)果: // 1 // 2 // 異步
為什么JavaScript要采用異步編程
一開始就說(shuō)過(guò),JavaScript是一種單線程執(zhí)行的腳本語(yǔ)言(這可能是由于歷史原因或?yàn)榱撕?jiǎn)單而采取的設(shè)計(jì))。它的單線程表現(xiàn)在任何一個(gè)函數(shù)都要從頭到尾執(zhí)行完畢之后,才會(huì)執(zhí)行另一個(gè)函數(shù),界面的更新、鼠標(biāo)事件的處理、計(jì)時(shí)器(setTimeout、setInterval等)的執(zhí)行也需要先排隊(duì),后串行執(zhí)行。假如有一段JavaScript從頭到尾執(zhí)行時(shí)間比較長(zhǎng),那么在執(zhí)行期間任何UI更新都會(huì)被阻塞,界面事件處理也會(huì)停止響應(yīng)。這種情況下就需要異步編程模式,目的就是把代碼的運(yùn)行打散或者讓IO調(diào)用(例如AJAX)在后臺(tái)運(yùn)行,讓界面更新和事件處理能夠及時(shí)地運(yùn)行。
JavaScript語(yǔ)言的設(shè)計(jì)者意識(shí)到,這時(shí)主線程完全可以不管IO設(shè)備,掛起處于等待中的任務(wù),先運(yùn)行排在后面的任務(wù)。等到IO設(shè)備返回了結(jié)果,再回過(guò)頭,把掛起的任務(wù)繼續(xù)執(zhí)行下去。
異步運(yùn)行機(jī)制:
所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧。
主線程之外,還存在一個(gè)任務(wù)隊(duì)列。只要異步任務(wù)有了運(yùn)行結(jié)果,就在任務(wù)隊(duì)列之中放置一個(gè)事件。
一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列,看看里面有哪些事件。那些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開始執(zhí)行。
主線程不斷重復(fù)上面的第三步。
舉個(gè)例子:
點(diǎn)擊同步按鈕會(huì)調(diào)用updateSync的同步函數(shù),邏輯非常簡(jiǎn)單,循環(huán)體內(nèi)每次更新output結(jié)點(diǎn)的內(nèi)容為i。如果在其他多線程模型下的語(yǔ)言,你可能會(huì)看到界面上以非??斓乃俣蕊@示從0到999999后停止。但是在JavaScript中,你會(huì)感覺(jué)按鈕按下去的時(shí)候卡了一下,然后看到一個(gè)最終結(jié)果999999,而沒(méi)有中間過(guò)程,這就是因?yàn)樵?b>updateSync函數(shù)運(yùn)行過(guò)程中UI更新被阻塞,只有當(dāng)它結(jié)束退出后才會(huì)更新UI。反之,當(dāng)點(diǎn)擊異步的時(shí)候,會(huì)明顯的看到Dom在逐步更新的過(guò)程。
從上面的例子中可以明顯的看出,異步編程對(duì)于JavaScript來(lái)說(shuō)是多么多么的重要。
異步編程有什么好處
從編程方式來(lái)講當(dāng)然是同步編程的方式更為簡(jiǎn)單,但是同步有其局限性一是假如是單線程那么一旦遇到阻塞調(diào)用,會(huì)造成整個(gè)線程阻塞,導(dǎo)致cpu無(wú)法得到有效利用,而瀏覽器的JavaScript執(zhí)行和瀏覽器渲染是運(yùn)行在單線程中,一旦遇到阻塞調(diào)用不僅意味JavaScript的執(zhí)行被阻塞更意味整個(gè)瀏覽器渲染也被阻塞這就導(dǎo)致界面的卡死,若是多線程則不可避免的要考慮互斥和同步問(wèn)題,而互斥和同步帶來(lái)復(fù)雜度也很大,實(shí)際上瀏覽器下因?yàn)橥瑫r(shí)只能執(zhí)行一段JavaScript代碼這意味著不存在互斥問(wèn)題,但是同步問(wèn)題仍然不可避免,以往回調(diào)風(fēng)格中異步的流程控制(其實(shí)就是同步問(wèn)題)也比較復(fù)雜。瀏覽器端的編程方式也即是GUI編程,其本質(zhì)就是事件驅(qū)動(dòng)的(鼠標(biāo)點(diǎn)擊,Http請(qǐng)求結(jié)束等)異步編程更為自然。
突然有個(gè)疑問(wèn),既然如此為什么JavaScript沒(méi)有使用多線程作業(yè)呢?就此就去Google了一下JavaScript多線程,在HTML5推出之后是提供了多線程只是比較局限。在使用多線程的時(shí)候無(wú)法使用window對(duì)象。若JavaScript使用多線程,在A線程中正在操作DOM,但是B線程中已經(jīng)把該DOM已經(jīng)刪除了(只是簡(jiǎn)單的小栗子,可能還有很多問(wèn)題,至于這些歷史問(wèn)題無(wú)從考究了)。會(huì)給編程作業(yè)帶來(lái)很大的負(fù)擔(dān)。就我而言我想這也就說(shuō)明了為什么JavaScript沒(méi)有使用多線程的原因吧。
異步與回調(diào)
回調(diào)到底屬于異步么?會(huì)想起剛剛開始學(xué)習(xí)JavaScript的時(shí)候常常吧這兩個(gè)概念混合在一起。在搞清楚這個(gè)問(wèn)題,首先要明白什么是回調(diào)函數(shù)。
百科:回調(diào)函數(shù)是一個(gè)函數(shù),它作為參數(shù)傳遞給另一個(gè)函數(shù),并在父函數(shù)完成后執(zhí)行?;卣{(diào)的特殊之處在于,出現(xiàn)在“父類”之后的函數(shù)可以在回調(diào)執(zhí)行之前執(zhí)行。另一件需要知道的重要事情是如何正確地傳遞回調(diào)。這就是我經(jīng)常忘記正確語(yǔ)法的地方。
通過(guò)上面的解釋可以得出,回調(diào)函數(shù)本質(zhì)上其實(shí)就是一種設(shè)計(jì)模式,例如我們熟悉的JQuery也只不過(guò)是遵循了這個(gè)設(shè)計(jì)原則而已。在JavaScript中,回調(diào)函數(shù)具體的定義為:函數(shù)A作為參數(shù)(函數(shù)引用)傳遞到另一個(gè)函數(shù)B中,并且這個(gè)函數(shù)B執(zhí)行函數(shù)A。我們就說(shuō)函數(shù)A叫做回調(diào)函數(shù)。如果沒(méi)有名稱(函數(shù)表達(dá)式),就叫做匿名回調(diào)函數(shù)。
簡(jiǎn)單的舉個(gè)小例子:
function test (n,fn){ console.log(n); fn && fn(n); } console.log(1); test(2); test(3,function(n){ console.log(n+1) }); console.log(5) // 結(jié)果 // 1 // 2 // 3 // 4 // 5
通過(guò)上面的代碼輸出的結(jié)果可以得出回調(diào)函數(shù)不一定屬于異步,一般同步會(huì)阻塞后面的代碼,通過(guò)輸出結(jié)果也就得出了這個(gè)結(jié)論?;卣{(diào)函數(shù),一般在同步情境下是最后執(zhí)行的,而在異步情境下有可能不執(zhí)行,因?yàn)槭录](méi)有被觸發(fā)或者條件不滿足。
回調(diào)函數(shù)應(yīng)用場(chǎng)景
資源加載:動(dòng)態(tài)加載js文件后執(zhí)行回調(diào),加載iframe后執(zhí)行回調(diào),ajax操作回調(diào),圖片加載完成執(zhí)行回調(diào),AJAX等等。
DOM事件及Node.js事件基于回調(diào)機(jī)制(Node.js回調(diào)可能會(huì)出現(xiàn)多層回調(diào)嵌套的問(wèn)題)。
setTimeout的延遲時(shí)間為0,這個(gè)hack經(jīng)常被用到,settimeout調(diào)用的函數(shù)其實(shí)就是一個(gè)callback的體現(xiàn)
鏈?zhǔn)秸{(diào)用:鏈?zhǔn)秸{(diào)用的時(shí)候,在賦值器(setter)方法中(或者本身沒(méi)有返回值的方法中)很容易實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,而取值器(getter)相對(duì)來(lái)說(shuō)不好實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,因?yàn)槟阈枰≈灯鞣祷啬阈枰臄?shù)據(jù)而不是this指針,如果要實(shí)現(xiàn)鏈?zhǔn)椒椒?,可以用回調(diào)函數(shù)來(lái)實(shí)現(xiàn)。
setTimeout、setInterval的函數(shù)調(diào)用得到其返回值。由于兩個(gè)函數(shù)都是異步的,即:調(diào)用時(shí)序和程序的主流程是相對(duì)獨(dú)立的,所以沒(méi)有辦法在主體里面等待它們的返回值,它們被打開的時(shí)候程序也不會(huì)停下來(lái)等待,否則也就失去了setTimeout及setInterval的意義了,所以用return已經(jīng)沒(méi)有意義,只能使用callback。callback的意義在于將timer執(zhí)行的結(jié)果通知給代理函數(shù)進(jìn)行及時(shí)處理。
JavaScript中的那些異步操作
JavaScript既然有很多的輔助線程,不可能所有的工作都是通過(guò)主線程去做,既然分配給輔助線程去做事情。
XMLHttpRequest
XMLHttpRequest對(duì)象應(yīng)該不是很陌生的,主要用于瀏覽器的數(shù)據(jù)請(qǐng)求與數(shù)據(jù)交互。XMLHttpRequest對(duì)象提供兩種請(qǐng)求數(shù)據(jù)的方式,一種是同步,一種是異步??梢酝ㄟ^(guò)參數(shù)進(jìn)行配置。默認(rèn)為異步。
對(duì)于XMLHttpRequest這里就不作太多的贅述了。
var xhr = new XMLHttpRequest(); xhr.open("GET", url, false); //同步方式請(qǐng)求 xhr.open("GET", url, true); //異步 xhr.send();
同步Ajax請(qǐng)求:
當(dāng)請(qǐng)求開始發(fā)送時(shí),瀏覽器事件線程通知主線程,讓Http線程發(fā)送數(shù)據(jù)請(qǐng)求,主線程收到請(qǐng)求之后,通知Http線程發(fā)送請(qǐng)求,Http線程收到主線程通知之后就去請(qǐng)求數(shù)據(jù),等待服務(wù)器響應(yīng),過(guò)了N年之后,收到請(qǐng)求回來(lái)的數(shù)據(jù),返回給主線程數(shù)據(jù)已經(jīng)請(qǐng)求完成,主線程把結(jié)果返回給了瀏覽器事件線程,去完成后續(xù)操作。
異步Ajax請(qǐng)求:
當(dāng)請(qǐng)求開始發(fā)送時(shí),瀏覽器事件線程通知,瀏覽器事件線程通知主線程,讓Http線程發(fā)送數(shù)據(jù)請(qǐng)求,主線程收到請(qǐng)求之后,通知Http線程發(fā)送請(qǐng)求,Http線程收到主線程通知之后就去請(qǐng)求數(shù)據(jù),并通知主線程請(qǐng)求已經(jīng)發(fā)送,主進(jìn)程通知瀏覽器事件線程已經(jīng)去請(qǐng)求數(shù)據(jù),則
瀏覽器事件線程,只需要等待結(jié)果,并不影響其他工作。
setInterval&setTimeout
setInterval與setTimeout同屬于異步方法,其異步是通過(guò)回調(diào)函數(shù)方式實(shí)現(xiàn)。其兩者的區(qū)別則setInterval會(huì)連續(xù)調(diào)用回調(diào)函數(shù),則setTimeout會(huì)延時(shí)調(diào)用回調(diào)函數(shù)只會(huì)執(zhí)行一次。
setInterval(() => { alert(1) },2000) // 每隔2s彈出一次1 setTimeout(() => { alert(2) },2000) // 進(jìn)入頁(yè)面后2s彈出2,則不會(huì)再次彈出
requestAnimationFarme
requestAnimationFrame字面意思就是去請(qǐng)求動(dòng)畫幀,在沒(méi)有API之前都是基于setInterval,與setInterval相比,requestAnimationFrame最大的優(yōu)勢(shì)是由系統(tǒng)來(lái)決定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)。具體一點(diǎn)講,如果屏幕刷新率是60Hz,那么回調(diào)函數(shù)就每16.7ms被執(zhí)行一次,如果刷新率是75Hz,那么這個(gè)時(shí)間間隔就變成了1000/75=13.3ms,換句話說(shuō)就是,requestAnimationFrame的步伐跟著系統(tǒng)的刷新步伐走。它能保證回調(diào)函數(shù)在屏幕每一次的刷新間隔中只被執(zhí)行一次,這樣就不會(huì)引起丟幀現(xiàn)象,也不會(huì)導(dǎo)致動(dòng)畫出現(xiàn)卡頓的問(wèn)題。
舉個(gè)小例子:
var progress = 0; //回調(diào)函數(shù) function render() { progress += 1; //修改圖像的位置 if (progress < 100) { //在動(dòng)畫沒(méi)有結(jié)束前,遞歸渲染 window.requestAnimationFrame(render); } } //第一幀渲染 window.requestAnimationFrame(render);
Object.observe - 觀察者
Object.observe是一個(gè)提供數(shù)據(jù)監(jiān)視的API,在chrome中已經(jīng)可以使用。是ECMAScript 7 的一個(gè)提案規(guī)范,官方建議的是謹(jǐn)慎使用級(jí)別,但是個(gè)人認(rèn)為這個(gè)API非常有用,例如可以對(duì)現(xiàn)在流行的MVVM框架作一些簡(jiǎn)化和優(yōu)化。雖然標(biāo)準(zhǔn)還沒(méi)定,但是標(biāo)準(zhǔn)往往是滯后于實(shí)現(xiàn)的,只要是有用的東西,肯定會(huì)有越來(lái)越多的人去使用,越來(lái)越多的引擎會(huì)支持,最終促使標(biāo)準(zhǔn)的生成。從observe字面意思就可以知道,這玩意兒就是用來(lái)做觀察者模式之類。
var obj = {a: 1}; Object.observe(obj, output); obj.b = 2; obj.a = 2; Object.defineProperties(obj, {a: { enumerable: false}}); //修改屬性設(shè)定 delete obj.b; function output(change) { console.log(1) }
Promise
Promise是對(duì)異步編程的一種抽象。它是一個(gè)代理對(duì)象,代表一個(gè)必須進(jìn)行異步處理的函數(shù)返回的值或拋出的異常。也就是說(shuō)Promise對(duì)象代表了一個(gè)異步操作,可以將異步對(duì)象和回調(diào)函數(shù)脫離開來(lái),通過(guò)then方法在這個(gè)異步操作上面綁定回調(diào)函數(shù)。
在Promise中最直觀的例子就是Promise.all統(tǒng)一去請(qǐng)求,返回結(jié)果。
var p1 = Promise.resolve(3); var p2 = 42; var p3 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "foo"); }); Promise.all([p1, p2, p3]).then(function(values) { console.log(values); }); // expected output: Array [3, 42, "foo"]
Generator&Async/Await
ES6的Generator卻給異步操作又提供了新的思路,馬上就有人給出了如何用Generator來(lái)更加優(yōu)雅的處理異步操作。Generator函數(shù)是協(xié)程在ES6的實(shí)現(xiàn),最大特點(diǎn)就是可以交出函數(shù)的執(zhí)行權(quán)(即暫停執(zhí)行)。整個(gè)Generator函數(shù)就是一個(gè)封裝的異步任務(wù),或者說(shuō)是異步任務(wù)的容器。異步操作需要暫停的地方,都用yield語(yǔ)句注明。Generator函數(shù)的執(zhí)行方法如下。
function * greneratorDome(){ yield "Hello"; yield "World"; return "Ending"; } let grenDome = greneratorDome(); console.log(grenDome.next()); // {value: "Hello", done: false} console.log(grenDome.next()); // {value: "World", done: false} console.log(grenDome.next()); // {value: "Ending", done: true} console.log(grenDome.next()); // {value: undefined, done: true}
粗略實(shí)現(xiàn)Generator
function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true}; } }; } var it = makeIterator(["a", "b"]); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true }
Async/Await與Generator類似,Async/await是Javascript編寫異步程序的新方法。以往的異步方法無(wú)外乎回調(diào)函數(shù)和Promise。但是Async/await建立于Promise之上,個(gè)人理解是使用了Generator函數(shù)做了語(yǔ)法糖。async函數(shù)就是隧道盡頭的亮光,很多人認(rèn)為它是異步操作的終極解決方案。
function a(){ return new Promise((resolve,reject) => { console.log("a函數(shù)") resolve("a函數(shù)") }) } function b (){ return new Promise((resolve,reject) => { console.log("b函數(shù)") resolve("b函數(shù)") }) } async function dome (){ let A = await a(); let B = await b(); return Promise.resolve([A,B]); } dome().then((res) => { console.log(res); });
Node.js異步I/O
當(dāng)我們發(fā)起IO請(qǐng)求時(shí),調(diào)用的是各個(gè)不同平臺(tái)的操作系統(tǒng)內(nèi)部實(shí)現(xiàn)的線程池內(nèi)的線程。這里的IO請(qǐng)求可不僅僅是讀寫磁盤文件,在*nix中,將計(jì)算機(jī)抽象了一層,磁盤文件、硬件、套接字等幾乎所有計(jì)算機(jī)資源都被抽象為文件,常說(shuō)的IO請(qǐng)求就是抽象后的文件。完成Node整個(gè)異步IO環(huán)節(jié)的有事件循環(huán)、觀察者、請(qǐng)求對(duì)象。
事件循環(huán)機(jī)制
單線程就意味著,所有任務(wù)需要排隊(duì),前一個(gè)任務(wù)結(jié)束,才會(huì)執(zhí)行后一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng),后一個(gè)任務(wù)就不得不一直等著。于是就有一個(gè)概念,任務(wù)隊(duì)列。如果排隊(duì)是因?yàn)橛?jì)算量大,CPU忙不過(guò)來(lái),倒也算了,但是很多時(shí)候CPU是閑著的,因?yàn)?b>IO設(shè)備(輸入輸出設(shè)備)很慢(比如Ajax操作從網(wǎng)絡(luò)讀取數(shù)據(jù)),不得不等著結(jié)果出來(lái),再往下執(zhí)行。
事件循環(huán)是Node的自身執(zhí)行模型,正是事件循環(huán)使得回調(diào)函數(shù)得以在Node中大量的使用。在進(jìn)程啟動(dòng)時(shí)Node會(huì)創(chuàng)建一個(gè)while(true)死循環(huán),這個(gè)和Netty也是一樣的,每次執(zhí)行循環(huán)體,都會(huì)完成一次Tick。每個(gè)Tick的過(guò)程就是查看是否有事件等待被處理。如果有,就取出事件及相關(guān)的回調(diào)函數(shù),并執(zhí)行關(guān)聯(lián)的回調(diào)函數(shù)。如果不再有事件處理就退出進(jìn)程。
線程只會(huì)做一件事情,就是從事件隊(duì)列里面取事件、執(zhí)行事件,再取事件、再事件。當(dāng)消息隊(duì)列為空時(shí),就會(huì)等待直到消息隊(duì)列變成非空。而且主線程只有在將當(dāng)前的消息執(zhí)行完成后,才會(huì)去取下一個(gè)消息。這種機(jī)制就叫做事件循環(huán)機(jī)制,取一個(gè)消息并執(zhí)行的過(guò)程叫做一次循環(huán)。
while(true) { var message = queue.get(); execute(message); }
我們可以把整個(gè)事件循環(huán)想象成一個(gè)事件隊(duì)列,在進(jìn)入事件隊(duì)列時(shí)開始對(duì)事件進(jìn)行彈出操作,直至事件為0為止。
process.nextTick
process.nextTick()方法可以在當(dāng)前"執(zhí)行棧"的尾部-->下一次Event Loop(主線程讀取"任務(wù)隊(duì)列")之前-->觸發(fā)process指定的回調(diào)函數(shù)。也就是說(shuō),它指定的任務(wù)總是發(fā)生在所有異步任務(wù)之前,當(dāng)前主線程的末尾。(nextTick雖然也會(huì)異步執(zhí)行,但是不會(huì)給其他io事件執(zhí)行的任何機(jī)會(huì));
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function C() { console.log(3"); }, 0); // 1 // 2 // 3
異步過(guò)程的構(gòu)成要素
異步函數(shù)實(shí)際上很快就調(diào)用完成了,但是后面還有工作線程執(zhí)行異步任務(wù),通知主線程,主線程調(diào)用回調(diào)函數(shù)等很多步驟。我們把整個(gè)過(guò)程叫做異步過(guò)程,異步函數(shù)的調(diào)用在整個(gè)異步過(guò)程中只是一小部分。
一個(gè)異步過(guò)程的整個(gè)過(guò)程:主線程發(fā)一起一個(gè)異步請(qǐng)求,相應(yīng)的工作線程接收請(qǐng)求并告知主線程已收到通知(異步函數(shù)返回);主線程可以繼續(xù)執(zhí)行后面的代碼,同時(shí)工作線程執(zhí)行異步任務(wù);工作線程完成工作后,通知主線程;主線程收到通知后,執(zhí)行一定的動(dòng)作(調(diào)用回調(diào)函數(shù))。
它可以叫做異步過(guò)程的發(fā)起函數(shù),或者叫做異步任務(wù)注冊(cè)函數(shù)。args是這個(gè)函數(shù)需要的參數(shù),callbackFn(回調(diào)函數(shù))也是這個(gè)函數(shù)的參數(shù),但是它比較特殊所以多帶帶列出來(lái)。所以,從主線程的角度看,一個(gè)異步過(guò)程包括下面兩個(gè)要素:
發(fā)起函數(shù);
回調(diào)函數(shù)callbackFn
它們都是主線程上調(diào)用的,其中注冊(cè)函數(shù)用來(lái)發(fā)起異步過(guò)程,回調(diào)函數(shù)用來(lái)處理結(jié)果。
舉個(gè)具體的栗子:
setTimeout(function,1000);
其中setTimeout就是異步過(guò)程的發(fā)起函數(shù),function是回調(diào)函數(shù)。
注:前面說(shuō)得形式A(args...,callbackFn)只是一種抽象的表示,并不代表回調(diào)函數(shù)一定要作為發(fā)起函數(shù)的參數(shù),例如:
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = xxx; xhr.open("GET", url); xhr.send();
總結(jié)
JavaScript的異步編程模式不僅是一種趨勢(shì),而且是一種必要,因此作為HTML5開發(fā)者是非常有必要掌握的。采用第三方的異步編程庫(kù)和異步同步化的方法,會(huì)讓代碼結(jié)構(gòu)相對(duì)簡(jiǎn)潔,便于維護(hù),推薦開發(fā)人員掌握一二,提高團(tuán)隊(duì)開發(fā)效率。
如果你和我一樣喜歡前端的話,可以加Qq群:135170291,期待大家的加入。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109736.html
摘要:同時(shí),如果執(zhí)行的過(guò)程中發(fā)現(xiàn)其他函數(shù),繼續(xù)入棧然后執(zhí)行。上面我們討論的其實(shí)都是同步代碼,代碼在運(yùn)行的時(shí)候只用調(diào)用棧解釋就可以了。 序 Event Loop 這個(gè)概念相信大家或多或少都了解過(guò),但是有一次被一個(gè)小伙伴問(wèn)到它具體的原理的時(shí)候,感覺(jué)自己只知道個(gè)大概印象,于是計(jì)劃著寫一篇文章,用輸出倒逼輸入,讓自己重新學(xué)習(xí)這個(gè)概念,同時(shí)也能幫助更多的人理解它~ 概念 JavaScript 是一門 ...
摘要:而在單線程環(huán)境下,繞不過(guò)錯(cuò)誤就意味著導(dǎo)致應(yīng)用退出,重啟恢復(fù)的間隙會(huì)導(dǎo)致服務(wù)中斷,這是我們不愿意看到的。這也是支持高并發(fā)的重要原因之一實(shí)際上不光是操作,的絕大多數(shù)操作都是以這種異步的方式進(jìn)行的。 本文首發(fā)于我的個(gè)人博客: kmknkk.xin 不足之處歡迎斧正! Node特性:高并發(fā) 在解釋node為什么能夠做到高并發(fā)之前,不妨先了解一下node的其他幾個(gè)特性: 單線程 我們先來(lái)明確...
摘要:如果響應(yīng)的內(nèi)容類型是或,這個(gè)屬性將保存包含著響應(yīng)數(shù)據(jù)的文檔。響應(yīng)的狀態(tài)狀態(tài)的說(shuō)明當(dāng)對(duì)象把一個(gè)請(qǐng)求發(fā)送到服務(wù)器的過(guò)程中會(huì)經(jīng)歷幾個(gè)狀態(tài),直到請(qǐng)求被處理,然后才接收一個(gè)回應(yīng)。 1.什么是Ajax Ajax:是Asynchronous Javascript And XML的簡(jiǎn)寫,即異步JavaScript和XML用途:動(dòng)態(tài)刷新局部數(shù)據(jù),無(wú)需卸載整個(gè)頁(yè)面,從而帶來(lái)更好的用戶體驗(yàn)Ajax核心:XM...
摘要:調(diào)用棧是一種棧結(jié)構(gòu)它用來(lái)存儲(chǔ)計(jì)算機(jī)程序執(zhí)行時(shí)候其活躍子程序的信息。調(diào)用棧是解析器的一種機(jī)制。并形成一個(gè)棧幀任何被這個(gè)函數(shù)調(diào)用的函數(shù)會(huì)進(jìn)一步添加到調(diào)用棧中,形成另一個(gè)棧幀并且運(yùn)行到它們被上個(gè)程序調(diào)用的位置。然后調(diào)用棧繼續(xù)運(yùn)行其他部門。 大家在進(jìn)行javascript開發(fā)的時(shí)候,有沒(méi)有想過(guò),我們寫的代碼是怎么樣運(yùn)行的呢?下面我們就來(lái)剖析一下代碼的執(zhí)行過(guò)程。 一 什么是調(diào)用棧 代碼在運(yùn)行過(guò)程...
摘要:核心的異步延遲函數(shù),用于異步延遲調(diào)用函數(shù)優(yōu)先使用原生原本支持更廣,但在的中,觸摸事件處理程序中觸發(fā)會(huì)產(chǎn)生嚴(yán)重錯(cuò)誤的,回調(diào)被推入隊(duì)列但是隊(duì)列可能不會(huì)如期執(zhí)行。 淺析 Vue 2.6 中的 nextTick 方法。 事件循環(huán) JS 的 事件循環(huán) 和 任務(wù)隊(duì)列 其實(shí)是理解 nextTick 概念的關(guān)鍵。這個(gè)網(wǎng)上其實(shí)有很多優(yōu)質(zhì)的文章做了詳細(xì)介紹,我就簡(jiǎn)單過(guò)過(guò)了。 以下內(nèi)容適用于瀏覽器端 JS,...
閱讀 803·2023-04-26 00:30
閱讀 2711·2021-11-23 09:51
閱讀 1059·2021-11-02 14:38
閱讀 2616·2021-09-07 10:23
閱讀 2257·2021-08-21 14:09
閱讀 1404·2019-08-30 10:57
閱讀 1615·2019-08-29 11:20
閱讀 1163·2019-08-26 13:53