日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JavaScript 异步执行的学习笔记 - 什么是事件循环 Event loop?

發(fā)布時間:2023/12/19 javascript 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript 异步执行的学习笔记 - 什么是事件循环 Event loop? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文

使用像 JavaScript 這樣的語言進行編程時,最重要但也經(jīng)常被誤解的部分之一是如何表達和操作一段需要某段時間才能完成執(zhí)行的程序行為。

這不僅僅是從 for 循環(huán)開始到 for 循環(huán)結(jié)束發(fā)生的事情,這當然需要一些時間(微秒到毫秒)才能完成。它是關(guān)于當你的程序的一部分現(xiàn)在運行而你的程序的另一部分稍后運行時會發(fā)生什么。在程序的兩部分分別得到執(zhí)行的時間間隙,存在著一個 gap.

實際上,所有編寫過的重要程序(尤其是用 JS 編寫的)都必須以某種方式管理這個 gap,無論是等待用戶輸入、從數(shù)據(jù)庫或文件系統(tǒng)請求數(shù)據(jù)、通過網(wǎng)絡(luò)發(fā)送數(shù)據(jù)以及等待響應(yīng),或以固定的時間間隔執(zhí)行重復(fù)的任務(wù)(如動畫)。通過所有這些不同的方式,您的程序必須及時管理狀態(tài)。

異步編程從 JS 開始就已經(jīng)存在了,這是肯定的。但是大多數(shù) JS 開發(fā)人員從來沒有真正仔細考慮過它是如何以及為什么會出現(xiàn)在他們的程序中,或者探索各種其他方法來處理它。足夠好的方法一直是不起眼的回調(diào)函數(shù)。直到今天,許多人仍堅持認為回調(diào)已綽綽有余。

但是隨著 JS 的范圍和復(fù)雜性不斷增長,為了滿足在瀏覽器和服務(wù)器以及介于兩者之間的所有可能的設(shè)備中運行的一流編程語言不斷擴大的需求,我們管理異步的痛苦正變得越來越嚴重,他們迫切需要更有能力和更合理的方法。

我們必須更深入地了解異步是什么以及它如何在 JS 中運行。

a program in chunks

你可以在一個 .js 文件中編寫你的 JS 程序,但你的程序幾乎肯定由幾個塊組成,其中只有一個現(xiàn)在要執(zhí)行,其余的將稍后執(zhí)行。 每個塊最常見的單位是函數(shù)。

大多數(shù)剛接觸 JS 的開發(fā)人員似乎都有的問題是,“l(fā)ater”不會嚴格地發(fā)生在“now”之后。 換句話說,根據(jù)定義,當前無法完成的任務(wù)將異步完成,因此我們不會像您直觀地期望或想要的那樣有阻塞行為。

考慮:

// ajax(..) is some arbitrary Ajax function given by a library var data = ajax( "http://some.url.1" );console.log( data ); // Oops! `data` generally won't have the Ajax results

您可能知道標準 Ajax 請求不是同步完成的,這意味著 ajax(…) 函數(shù)還沒有任何返回值以分配給 data 變量。 如果 ajax(…) 可以阻塞直到響應(yīng)回來,那么 data = … 賦值會正常工作。

但這不是我們使用 Ajax 的方式。 我們現(xiàn)在發(fā)出一個異步的 Ajax 請求,直到稍后我們才會得到結(jié)果。

從現(xiàn)在到以后“等待”的最簡單(但絕對不僅僅是,甚至最好!)方法是使用一個函數(shù),通常稱為回調(diào)函數(shù):

// ajax(..) is some arbitrary Ajax function given by a library ajax( "http://some.url.1", function myCallbackFunction(data){console.log( data ); // Yay, I gots me some `data`!} );

看這段代碼:

function now() {return 21; }function later() {answer = answer * 2;console.log( "Meaning of life:", answer ); }var answer = now();setTimeout( later, 1000 ); // Meaning of life: 42

這個程序有兩個部分:現(xiàn)在將運行的內(nèi)容和稍后運行的內(nèi)容。 這兩個塊是什么應(yīng)該很明顯。

現(xiàn)在立即執(zhí)行的代碼塊:

function now() {return 21; }function later() { .. }var answer = now();setTimeout( later, 1000 );

稍后異步執(zhí)行的代碼塊:

answer = answer * 2; console.log( "Meaning of life:", answer );

一旦您執(zhí)行程序, now 塊就會立即運行。 但是 setTimeout(…) 也會設(shè)置一個事件(超時)稍后發(fā)生,因此 later() 函數(shù)的內(nèi)容將在稍后的時間(從現(xiàn)在起 1,000 毫秒)執(zhí)行。

任何時候您將一部分代碼包裝到一個函數(shù)中并指定它應(yīng)該響應(yīng)某些事件(計時器、鼠標單擊、Ajax 響應(yīng)等)而執(zhí)行時,您正在創(chuàng)建代碼的“l(fā)ater”部分,從而引入異步到你的程序。

Async Console

console.log 到底是同步輸出還是異步輸出?

沒有關(guān)于 console.* 方法如何工作的規(guī)范或一組要求——它們不是 JavaScript 的正式組成部分,而是由托管環(huán)境添加到 JS 中。

因此,不同的瀏覽器和 JS 環(huán)境有著各自的實現(xiàn),這有時會導(dǎo)致混亂的行為。

特別是,有一些瀏覽器和一些條件,console.log(…) 實際上并沒有立即輸出它給出的內(nèi)容。 這可能發(fā)生的主要原因是因為 I/O 是許多程序(不僅僅是 JS)的一個非常緩慢和阻塞的部分。 因此,瀏覽器在后臺異步處理控制臺 I/O 可能會表現(xiàn)得更好(從頁面/UI 角度來看),而您甚至可能不知道發(fā)生了這種情況。

一個不太常見但可能的場景,可以觀察到這種情況(不是從代碼本身而是從外部):

var a = {index: 1 };// later console.log( a ); // ??// even later a.index++;

我們通常希望在 console.log(…) 語句的確切時刻看到 a 對象被快照,打印類似 { index: 1 } 的內(nèi)容,這樣在 a.index++ 發(fā)生時的下一個語句中,它正在修改與 a. 的輸出不同的東西,或者完全不同的東西。

大多數(shù)情況下,前面的代碼可能會在您的開發(fā)人員工具的控制臺中生成您所期望的對象表示。但同樣的代碼可能會在瀏覽器認為需要將控制臺 I/O 推遲到后臺的情況下運行,在這種情況下,當對象在瀏覽器控制臺中表示時,a.index++已經(jīng)發(fā)生了,它顯示 { index: 2 }。

在什么條件下控制臺 I/O 將被推遲,甚至是否可以觀察到,這是一個不斷變化的目標。請注意 I/O 中這種可能的異步性,以防您在調(diào)試中遇到問題,其中在 console.log(…) 語句之后修改了對象,但您看到意外的修改出現(xiàn)。

Event Loop

讓我們做出一個(也許令人震驚的)聲明:盡管您顯然能夠編寫異步 JS 代碼(例如我們剛剛看到的超時),但直到最近(ES6),JavaScript 本身實際上從未有任何內(nèi)置的異步的直接概念.

什么!?這似乎是一個瘋狂的主張,對吧?事實上,這是非常正確的。 JS 引擎本身從來沒有做過任何事情,只是在任何給定的時刻,在被要求時執(zhí)行你的程序的單個塊。

被誰要求執(zhí)行呢?這個問題很關(guān)鍵。

JS 引擎不是孤立運行的。它在托管環(huán)境中運行,對于大多數(shù)開發(fā)人員來說,這是典型的 Web 瀏覽器。在過去的幾年里(但絕不是唯一的),JS 通過 Node.js 之類的東西從瀏覽器擴展到其他環(huán)境,例如服務(wù)器。事實上,如今 JavaScript 被嵌入到各種設(shè)備中,從機器人到燈泡。

但是所有這些環(huán)境的一個共同“線程”是它們中有一種機制來處理隨著時間的推移來執(zhí)行多個程序塊,在每個時間點調(diào)用JS 引擎。這個線程稱為事件循環(huán)。

換句話說,JS 引擎并沒有與生俱來的時間感,而是一個任意 JS 片段的按需執(zhí)行環(huán)境。總是安排“事件”(即 JS 代碼執(zhí)行)的是執(zhí)行 JavaScript 代碼的托管環(huán)境。

因此,例如,當您的 JS 程序發(fā)出 Ajax 請求以從服務(wù)器獲取一些數(shù)據(jù)時,您在函數(shù)中設(shè)置響應(yīng)代碼(通常稱為回調(diào)),JS 引擎告訴托管環(huán)境,“嘿,我現(xiàn)在將暫停執(zhí)行,但是每當您完成該網(wǎng)絡(luò)請求并且您有一些數(shù)據(jù)時,請回調(diào)此函數(shù)。”

然后瀏覽器被設(shè)置為監(jiān)聽來自網(wǎng)絡(luò)的響應(yīng),當它有東西給你時,瀏覽器將回調(diào)函數(shù)插入到事件循環(huán)中,以此來調(diào)度回調(diào)函數(shù)的執(zhí)行。

那么什么是事件循環(huán)呢?

讓我們首先通過一些假代碼來概念化它。

事件循環(huán)(event loop)的邏輯可以用下面的偽代碼來表示:

// `eventLoop` is an array that acts as a queue // (first-in, first-out) var eventLoop = [ ]; var event;// keep going "forever" while (true) {// perform a "tick"if (eventLoop.length > 0) {// get the next event in the queueevent = eventLoop.shift();// now, execute the next eventtry {event();}catch (err) {reportError(err);}} }

當然,這是為了說明概念而大大簡化的偽代碼。但這應(yīng)該足以幫助獲得更好的理解。

如您所見,while 循環(huán)代表了一個持續(xù)運行的循環(huán),該循環(huán)的每次迭代稱為一個滴答。對于每個滴答聲,如果一個事件在隊列中等待,它就會被從隊列里摘下并執(zhí)行。這些事件是您的函數(shù)回調(diào)。

重要的是要注意 setTimeout(…) 不會將您的回調(diào)放在事件循環(huán)隊列中。它的作用是設(shè)置一個計時器;當計時器到期時,環(huán)境會將您的回調(diào)放入事件循環(huán)中,以便將來某個滴答聲將其拾取并執(zhí)行。

如果此時事件循環(huán)中已經(jīng)有 20 個項目怎么辦?您的回調(diào)等待。它排在其他人后面——通常沒有用于搶占隊列和跳過隊列的路徑。這解釋了為什么 setTimeout(…) 計時器可能無法以完美的時間精度觸發(fā)。您可以保證(粗略地說)您的回調(diào)不會在您指定的時間間隔之前觸發(fā),但它可以在該時間或之后發(fā)生,具體取決于事件隊列的狀態(tài)。

因此,換句話說,您的程序通常被分解成許多小塊,這些小塊在事件循環(huán)隊列中一個接一個地發(fā)生。從技術(shù)上講,與您的程序不直接相關(guān)的其他事件也可以在隊列中交錯。

Parallel Threading

將術(shù)語“異步 async”和“并行 parallel”混為一談是很常見的,但它們實際上是完全不同的。 請記住,異步是關(guān)于現(xiàn)在和以后之間的gap. 但并行是指事物能夠同時(simultaneously)發(fā)生。

最常見的并行計算工具是進程和線程。 進程和線程獨立執(zhí)行,也可能同時執(zhí)行:在不同的處理器上,甚至在不同的計算機上,但多個線程可以共享單個進程的內(nèi)存。

相比之下,事件循環(huán)將其工作分解為任務(wù)并串行執(zhí)行,不允許并行訪問和更改共享內(nèi)存。 并行和串行可以在不同線程中以協(xié)作事件循環(huán)的形式共存。

并行執(zhí)行線程的交織和異步事件的交織發(fā)生在非常不同的粒度級別。

例如:

function later() {answer = answer * 2;console.log( "Meaning of life:", answer ); }

雖然 later() 的全部內(nèi)容將被視為單個事件循環(huán)隊列條目,但在考慮運行此代碼的線程時,實際上可能有十幾種不同的低級操作。 例如,answer = answer * 2 需要首先加載 answer 的當前值,然后將 2 放在某處,然后執(zhí)行乘法,然后取結(jié)果并將其存儲回 answer。

在單線程環(huán)境中,線程隊列中的項是低級操作真的沒有關(guān)系,因為沒有什么可以中斷線程。 但是如果你有一個并行系統(tǒng),其中兩個不同的線程在同一個程序中運行,你很可能會出現(xiàn)不可預(yù)測的行為。

考慮下面這段代碼:

var a = 20;function foo() {a = a + 1; }function bar() {a = a * 2; }// ajax(..) is some arbitrary Ajax function given by a library ajax( "http://some.url.1", foo ); ajax( "http://some.url.2", bar );

總結(jié)

以上是生活随笔為你收集整理的JavaScript 异步执行的学习笔记 - 什么是事件循环 Event loop?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。