摘要:當(dāng)函數(shù)結(jié)束,將會(huì)被從調(diào)用棧移出。事件循環(huán)事件循環(huán)的責(zé)任就是查看調(diào)用棧并確定調(diào)用棧是否為空。事件循環(huán)會(huì)再次檢查調(diào)用棧是否為空,如果為空的話(huà),它會(huì)把事件回調(diào)壓入棧中,然后回調(diào)函數(shù)則被執(zhí)行。
寫(xiě)在文章前
這篇文章是翻譯自Sukhjinder Arora的
Understanding Asynchronous JavaScript。這篇文章描述了異步和同步JavaScript是如何在運(yùn)行環(huán)境中,使用調(diào)用棧,消息隊(duì)列,作業(yè)隊(duì)列,以及事件循環(huán)來(lái)工作的。文章如有翻譯不好的地方還望多多包涵。
眾所周知,JavaScript 是單線(xiàn)程的編程語(yǔ)言,那就意味著在同一個(gè)時(shí)間只能有一件事發(fā)生。通俗的講,JavaScript引擎每一個(gè)線(xiàn)程一次只能處理一個(gè)聲明。
雖然單線(xiàn)程語(yǔ)言可以簡(jiǎn)化寫(xiě)代碼的過(guò)程,因?yàn)槟悴挥脫?dān)心并發(fā)的問(wèn)題,但這樣同時(shí)也意味著你無(wú)法在不鎖住主線(xiàn)程的情況下,執(zhí)行像網(wǎng)絡(luò)訪(fǎng)問(wèn)這種長(zhǎng)時(shí)間的操作。
想象一下,從API請(qǐng)求數(shù)據(jù)的這個(gè)情況。服務(wù)器可能需要一些時(shí)間來(lái)處理請(qǐng)求,同時(shí)阻塞主線(xiàn)程使網(wǎng)頁(yè)無(wú)響應(yīng)。
這就是異步Javascript可以發(fā)揮作用的地方了。使用異步JavaScript(例如像回調(diào),promises,和async/await),你就可以在不鎖住主線(xiàn)程的情況下執(zhí)行長(zhǎng)時(shí)間的網(wǎng)絡(luò)請(qǐng)求。
你沒(méi)有必要學(xué)習(xí)所有這些概念來(lái)成為一個(gè)出色JavaScript工程師,這些只是對(duì)你很有幫助而已:)
所以廢話(huà)不多說(shuō),我們開(kāi)始吧。
同步JavaScript是怎么工作的呢?在我們深入了解異步JavaScript之前,讓我們先來(lái)了解一下同步的JavaScript代碼是如何在引擎內(nèi)部執(zhí)行的。舉個(gè)例子:
const second = () => { console.log("hello there"); } const first = () => { console.log("hi,there"); second(); console.log("The End"); } first();
在我們想要理解上面代碼是如何在JavaScript引擎執(zhí)行的之前,我們需要先要理解執(zhí)行上下文和調(diào)用棧的概念(也叫執(zhí)行棧)。
執(zhí)行上下文執(zhí)行上下文是JavaScript代碼被評(píng)估和執(zhí)行的地方的抽象概念。每當(dāng)任何js代碼執(zhí)行的時(shí)候,他們就運(yùn)行在執(zhí)行上下文內(nèi)部。
函數(shù)執(zhí)行在函數(shù)的執(zhí)行上下文內(nèi),全局代碼執(zhí)行在全局的執(zhí)行上下文內(nèi)。每個(gè)函數(shù)都有自己的執(zhí)行上下文。
調(diào)用棧調(diào)用棧就像他名字里展示的那樣,他是一個(gè)具有后進(jìn)先出的棧結(jié)構(gòu),它用于存儲(chǔ)代碼執(zhí)行期間創(chuàng)建的所有執(zhí)行上下文。
JavaScript是擁有單一調(diào)用棧的,因?yàn)樗菃尉€(xiàn)程的語(yǔ)言。調(diào)用棧的LIFO(后進(jìn)先出結(jié)構(gòu))決定了東西只能從棧的頂部添加或者刪除。
讓我們回到上面的代碼片段,然后嘗試?yán)斫庖幌律厦娴拇a片段是怎么在JavaScript引擎內(nèi)部執(zhí)行的。
const second = () => { console.log("hello there"); } const first = () => { console.log("hi,there"); second(); console.log("The End"); } first();
上面代碼的調(diào)用棧:
)
當(dāng)代碼執(zhí)行的時(shí)候,一個(gè)全局的執(zhí)行上下文就被創(chuàng)建了(表示為main())然后將他壓入調(diào)用棧的頂部。當(dāng)first()被調(diào)用的時(shí)候,first()又被壓入調(diào)用棧的頂部。
接下來(lái),console.log("hi,there")又被壓入棧的頂部,當(dāng)它執(zhí)行結(jié)束,他就從棧中彈出了。之后,我們調(diào)用了second(),所以second()函數(shù)就被壓入棧頂。
console.log("Hello there!")被壓入棧頂,并且當(dāng)它執(zhí)行結(jié)束就被彈出。 此時(shí),second()函數(shù)執(zhí)行結(jié)束,所以從棧中彈出。
console.log("The End")被壓入棧頂然后再結(jié)束的時(shí)候被移出。然后,first()函數(shù)執(zhí)行結(jié)束,被移出調(diào)用棧。
此時(shí),整個(gè)程序結(jié)束調(diào)用,所以全局執(zhí)行上下文(main())從棧中彈出。
異步JavaScript到底是怎么運(yùn)行的呢?現(xiàn)在我們已經(jīng)對(duì)調(diào)用棧有個(gè)大致了解了,也知道了同步的JavaScript是怎么工作的,現(xiàn)在我們回到異步JavaScript這個(gè)話(huà)題。
什么是鎖?我們想象一下我們正在使用同步的方式進(jìn)行圖像處理或者網(wǎng)絡(luò)請(qǐng)求。比如:
const processImage = (image) => { //對(duì)圖像進(jìn)行處理 console.log("Image Processed"); } const netWorkRequest = (url) => { //網(wǎng)絡(luò)資源請(qǐng)求 return someData; } const greeting = () => { console.log("Hello World"); } processImage(logo.jpg); networkRequest("www.somerandomurl.com"); greeting();
圖像的處理和網(wǎng)絡(luò)請(qǐng)求很花時(shí)間。所以當(dāng)processImage()函數(shù)被調(diào)用的時(shí)候,花費(fèi)的時(shí)間將取決于圖像的大小。
當(dāng)processImage()函數(shù)結(jié)束,將會(huì)被從調(diào)用棧移出。之后networkRequest()函數(shù)被調(diào)用并且被壓入棧中。所以又要花費(fèi)一些時(shí)間來(lái)結(jié)束調(diào)用。
最后當(dāng)networkRequest()函數(shù)結(jié)束,greeting()函數(shù)被調(diào)用,因?yàn)樗话粋€(gè)console.log聲明,而且console.log聲明執(zhí)行的非常地塊,所以greeting()函數(shù)很快的就結(jié)束調(diào)用了。
如你所見(jiàn),我們必須要等,等到函數(shù)(就像processImage()和networkRequest())結(jié)束執(zhí)行。這就意味著這些函數(shù)被鎖在調(diào)用棧或者主線(xiàn)程里。 所以在上述代碼執(zhí)行期間我們不能執(zhí)行任何其他的操作,這不絕不是我們想要的。
所以怎么解決?最簡(jiǎn)單的解決辦法就是異步回調(diào)。我們使用異步回調(diào)讓我們的代碼不被鎖住。舉個(gè)栗子:
const networkRequest = () => { setTimeout(() => { console.log("Async Code"); },2000); }; console.log("Hello World"); networkRequest();
在這里我使用了setTimeout方法來(lái)模擬網(wǎng)絡(luò)請(qǐng)求。請(qǐng)注意setTimeout不是Javascript引擎的一部分,它是Web Api(瀏覽器中)和 C/C++ (在node.js)中的一部分。
為了理解這段代碼是如何執(zhí)行的,我們需要理解更多的概念,比如像事件循環(huán)和回調(diào)隊(duì)列(也叫做任務(wù)隊(duì)列或者消息隊(duì)列)。
事件循環(huán),WEB API, 消息隊(duì)列/任務(wù)隊(duì)列不是JavaScript引擎的一部分,他們是瀏覽器的JavaScript運(yùn)行時(shí)環(huán)境或者Node.js JavaScript 運(yùn)行環(huán)境的一部分。 在Nodejs中,網(wǎng)絡(luò)接口被C/C++ API 取代.
現(xiàn)在,讓我們回到上面的代碼,然后看一看他們是怎么以異步的方式執(zhí)行的。
const networkRequest = () => { setTimeout(() => { console.log("Async Code"); }, 2000); }; console.log("Hello World"); networkRequest(); console.log("The End");
當(dāng)上面的代碼在瀏覽器加載的時(shí)候,console.log("Hello World")入棧并且當(dāng)調(diào)用結(jié)束的出棧。接下來(lái),調(diào)用的是networkRequest(),所以它被推入棧頂。
接下來(lái)setTimeout()方法被調(diào)用,所以被壓入棧頂。setTimeout函數(shù)有2個(gè)參數(shù):1) 回調(diào)函數(shù) 2)以ms為單位的時(shí)間。
setTimeout在Web API環(huán)境中開(kāi)始了一個(gè)為時(shí)2s的計(jì)時(shí)器。此時(shí),setTimeout已經(jīng)結(jié)束了,所以被彈出棧,接著,console.log("The End")被壓入棧,執(zhí)行然后在結(jié)束后從棧中移出。
與此同時(shí),計(jì)時(shí)器到時(shí)間了,現(xiàn)在回調(diào)被推入到信息隊(duì)列,但回調(diào)并沒(méi)有被立即執(zhí)行,而是被放到了事件循環(huán)開(kāi)始的地方。
事件循環(huán)事件循環(huán)的責(zé)任就是查看調(diào)用棧并確定調(diào)用棧是否為空。如果調(diào)用棧為空,他就會(huì)查看消息隊(duì)列來(lái)確定是否有任何掛起的回調(diào)函數(shù)等待被執(zhí)行。
在這個(gè)例子中消息隊(duì)列中包括一個(gè)回調(diào)函數(shù),并且此時(shí)調(diào)用棧為空。因此事件循環(huán)把回調(diào)函數(shù)壓入棧頂。
在那之后,console.log(‘Async Code‘)這條語(yǔ)句被壓入棧頂,執(zhí)行,然后從棧中彈出。此時(shí)回調(diào)函數(shù)結(jié)束了,所以它被從棧中彈出,然后整個(gè)程序結(jié)束執(zhí)行。
DOM 事件消息隊(duì)列中也包括DOM事件中的回調(diào)函數(shù)比如點(diǎn)擊事件和鍵盤(pán)事件,例如:
document.querySelector(".btn").addEventListener("click",(event) => { console.log("Button Clicked"); })
在DOM事件里,事件監(jiān)聽(tīng)器位于Web API 環(huán)境中等待某個(gè)事件發(fā)生(在這個(gè)例子中是點(diǎn)擊事件),并且當(dāng)該事件發(fā)生的時(shí)候,回調(diào)函數(shù)則被放置在消息隊(duì)列中等待被執(zhí)行。
事件循環(huán)會(huì)再次檢查調(diào)用棧是否為空,如果為空的話(huà),它會(huì)把事件回調(diào)壓入棧中,然后回調(diào)函數(shù)則被執(zhí)行。
我們已經(jīng)學(xué)習(xí)了異步回調(diào)和DOM 事件是如何執(zhí)行的,他們使用消息隊(duì)列來(lái)存儲(chǔ)所有等待被執(zhí)行的回調(diào)。
ES6 作業(yè)隊(duì)列/ 微任務(wù)隊(duì)列ES6介紹了一種被JavaScript 中Promises使用的叫做作業(yè)隊(duì)列/微任務(wù)隊(duì)列的概念。消息隊(duì)列和作業(yè)隊(duì)列的區(qū)別就在于作業(yè)隊(duì)列會(huì)比消息隊(duì)列擁有更高的優(yōu)先級(jí),也就是說(shuō)作業(yè)隊(duì)列/微任務(wù)隊(duì)列中的Promise的任務(wù)會(huì)比消息隊(duì)列中的回調(diào)函數(shù)先執(zhí)行。
例如:
console.log("Script start"); setTimeout(() => { console.log("setTimeout"); },0); new Promise((resolve,reject) => { resolve("Promise resolved"); }).then(res => console.log(res)) .catch(err => console.log(err)); console.log("Script End");
輸出:
Script start Script End Promise resolved setTimeout
我們可以看到promise是在setTimeout之前被執(zhí)行的,因?yàn)閜romise的返回是存儲(chǔ)在微任務(wù)隊(duì)列中的,它比消息隊(duì)列擁有更高的優(yōu)先級(jí)。
讓我們看下一個(gè)例子,這次有兩個(gè)Promises和兩個(gè)setTimeout。
console.log("Script start"); setTimeout(() => { console.log("setTimeout 1"); },0); setTimeout(() => { console.log("setTimeout 2"); },0); new Promise((resolve,reject) => { resolve("Promise 1 resolved"); }).then(res => console.log(res)) .catch(err => console.log(err)); new Promise((resolve,reject) => { resolve("Promise 2 resolved"); }).then(res => console.log(res)) .catch(err => console.log(err)); console.log("Script End");
這一次輸出:
Script start Script End Promise 1 resolved Promise 2 resolved setTimeout 1 setTimeout 2
我們可以看到兩個(gè)promise都是在setTimeout回調(diào)的前面執(zhí)行的,因?yàn)槭录h(huán)機(jī)制中,微任務(wù)隊(duì)列中的任務(wù)要優(yōu)先于消息隊(duì)列/任務(wù)隊(duì)列中的任務(wù)。
當(dāng)事件循環(huán)正在執(zhí)行微任務(wù)隊(duì)列中的任務(wù)時(shí),如果另一個(gè)promise處于resolved的狀態(tài)的話(huà),他會(huì)被添加到同一個(gè)微任務(wù)隊(duì)列的尾部,并且他會(huì)比消息隊(duì)列中的回調(diào)先執(zhí)行,不管回調(diào)函數(shù)已經(jīng)等待執(zhí)行了多久了。(優(yōu)先級(jí)高果然就是能為所欲為= =)。
舉個(gè)例子:
console.log("Script start"); setTimeout(() => { console.log("setTimeout"); }, 0); new Promise((resolve, reject) => { resolve("Promise 1 resolved"); }).then(res => console.log(res)); new Promise((resolve, reject) => { resolve("Promise 2 resolved"); }).then(res => { console.log(res); return new Promise((resolve, reject) => { resolve("Promise 3 resolved"); }) }).then(res => console.log(res)); console.log("Script End");
這次的輸出:
Script start Script End Promise 1 resolved Promise 2 resolved Promise 3 resolved setTimeout
所以所有在微任務(wù)隊(duì)列中的任務(wù)都將在消息隊(duì)列中的任務(wù)之前執(zhí)行。也就是說(shuō),事件循環(huán)將會(huì)在執(zhí)行任何消息隊(duì)列的回調(diào)之前,首先清空微任務(wù)隊(duì)列中的任務(wù)。
總結(jié)我們已經(jīng)學(xué)習(xí)了異步JavaScript是如何工作的,以及一些其他的概念比如說(shuō)調(diào)用棧,事件循環(huán),消息/任務(wù)隊(duì)列以及工作/微任務(wù)隊(duì)列,他們?cè)谝黄饦?gòu)成了JavaScript的運(yùn)行環(huán)境。再重申一下,雖然您沒(méi)有必要將這些所有的概念都學(xué)習(xí),來(lái)成為一個(gè)出色的JavaScript開(kāi)發(fā)人員,但了解這些概念會(huì)很有幫助:)
今天的文章就這樣啦,如果你覺(jué)得這篇文章對(duì)你很有幫助,請(qǐng)點(diǎn)擊旁邊的鼓掌按鈕,你也可以在Medium和Twitter上面follow我。如果你有任何的疑問(wèn),歡迎在下面留言,我會(huì)很開(kāi)心的幫助你的:)
譯者結(jié)語(yǔ)如果你對(duì)我的翻譯或者內(nèi)容有什么意見(jiàn)或者建議歡迎在下面留言告訴我,喜歡文章就給個(gè)贊吧,非常感謝您的閱讀,Hava a nice day:)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/99861.html
摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。寫(xiě)一個(gè)符合規(guī)范并可配合使用的寫(xiě)一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來(lái)處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問(wèn)題描述 在開(kāi)發(fā)過(guò)程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過(guò)http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過(guò)...
摘要:的翻譯文檔由的維護(hù)很多人說(shuō),阮老師已經(jīng)有一本關(guān)于的書(shū)了入門(mén),覺(jué)得看看這本書(shū)就足夠了。前端的異步解決方案之和異步編程模式在前端開(kāi)發(fā)過(guò)程中,顯得越來(lái)越重要。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(shū)(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書(shū)的目的是以目前還在制定中的ECMASc...
摘要:調(diào)用棧被清空,消息隊(duì)列中并無(wú)任務(wù),線(xiàn)程停止,事件循環(huán)結(jié)束。不確定的時(shí)間點(diǎn)請(qǐng)求返回,將設(shè)定好的回調(diào)函數(shù)放入消息隊(duì)列。調(diào)用棧執(zhí)行完畢執(zhí)行消息隊(duì)列任務(wù)。請(qǐng)求并發(fā)回調(diào)函數(shù)執(zhí)行順序無(wú)法確定。 異步編程 JavaScript中異步編程問(wèn)題可以說(shuō)是基礎(chǔ)中的重點(diǎn),也是比較難理解的地方。首先要弄懂的是什么叫異步? 我們的代碼在執(zhí)行的時(shí)候是從上到下按順序執(zhí)行,一段代碼執(zhí)行了之后才會(huì)執(zhí)行下一段代碼,這種方式...
摘要:從異步過(guò)程的角度看,函數(shù)就是異步過(guò)程的發(fā)起函數(shù),事件監(jiān)聽(tīng)函數(shù)就是異步過(guò)程的回調(diào)函數(shù)。事件觸發(fā)時(shí),表示異步任務(wù)完成,會(huì)將事件監(jiān)聽(tīng)器函數(shù)封裝成一條消息放到消息隊(duì)列中,等待主線(xiàn)程執(zhí)行。 1.為什么JavaScript是單線(xiàn)程? JavaScript語(yǔ)言的一大特點(diǎn)就是單線(xiàn)程,也就是說(shuō),同一個(gè)時(shí)間只能做一件事。那么,為什么JavaScript不能有多個(gè)線(xiàn)程呢?這樣能提高效率啊。JavaScrip...
摘要:異步本質(zhì)上應(yīng)該就是多線(xiàn)程語(yǔ)言的產(chǎn)物。如果是多線(xiàn)程的異步,假死的應(yīng)該是運(yùn)行方法的線(xiàn)程,而方法仍然會(huì)按預(yù)期打印出。當(dāng)然了,按我個(gè)人的理解,應(yīng)該說(shuō)是是的回調(diào)函數(shù)。 引子 每個(gè)故事都有由來(lái)。前兩天在看 gulp 的時(shí)候,看到了它有個(gè) promise 的玩意兒,然后的然后,這兩天就掉進(jìn)了 javascript 的異步和回調(diào)的坑里面去了。 其間搜索了 javascript promise,看到了...
摘要:在異步機(jī)制中,任務(wù)隊(duì)列就是用來(lái)維護(hù)異步任務(wù)回調(diào)函數(shù)的隊(duì)列。四對(duì)象對(duì)象是工作組提出的一種規(guī)范,目的是為異步編程提供統(tǒng)一接口。 異步 1.JavaScript單線(xiàn)程的理解 Javascript語(yǔ)言的執(zhí)行環(huán)境是單線(xiàn)程(single thread)。所謂單線(xiàn)程,就是指一次只能完成一件任務(wù)。如果有多個(gè)任務(wù),就必須排隊(duì),前面一個(gè)任務(wù)完成,再執(zhí)行后面一個(gè)任務(wù),以此類(lèi)推。 2.JavaScript單線(xiàn)...
閱讀 1087·2021-10-08 10:04
閱讀 3525·2021-08-05 10:01
閱讀 2283·2019-08-30 11:04
閱讀 1802·2019-08-29 15:29
閱讀 848·2019-08-29 15:12
閱讀 1674·2019-08-26 12:11
閱讀 3121·2019-08-26 11:33
閱讀 1167·2019-08-26 10:23