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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

从Javascript单线程谈Event Loop

發(fā)布時(shí)間:2023/12/31 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从Javascript单线程谈Event Loop 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

假如面試回答js的運(yùn)行機(jī)制時(shí),你可能說出這么一段話:“Javascript的事件分同步任務(wù)和異步任務(wù),遇到同步任務(wù)就放在執(zhí)行棧中執(zhí)行,而碰到異步任務(wù)就放到任務(wù)隊(duì)列之中,等到執(zhí)行棧執(zhí)行完畢之后再去執(zhí)行任務(wù)隊(duì)列之中的事件。”但你能說出背后的原因嗎?

?

先理解相關(guān)概念

線程與進(jìn)程

進(jìn)程:是系統(tǒng)資源分配和調(diào)度的單元。一個(gè)運(yùn)行著的程序就對(duì)應(yīng)了一個(gè)進(jìn)程。一個(gè)進(jìn)程包括了運(yùn)行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。

線程:線程是進(jìn)程下的執(zhí)行者,一個(gè)進(jìn)程至少會(huì)開啟一個(gè)線程(主線程),也可以開啟多個(gè)線程。

?

同步和異步

同步和異步關(guān)注的是消息結(jié)果通信機(jī)制

同步發(fā)出調(diào)用,在沒有得到結(jié)果前,該調(diào)用不返回。但是一旦調(diào)用返回,就得到返回值

異步發(fā)出調(diào)用后,調(diào)用直接返回,沒有返回結(jié)果。但結(jié)果由回調(diào)函數(shù)給出,至于什么時(shí)候給出,不知道。(這個(gè)回調(diào)函數(shù)肯定是在當(dāng)前js執(zhí)行完后才執(zhí)行)

?

阻塞與非阻塞

阻塞和非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果時(shí)的狀態(tài).

阻塞調(diào)用調(diào)用結(jié)果返回之前,當(dāng)前線程被掛起。調(diào)用線程只有在得到結(jié)果后才會(huì)返回。
非阻塞調(diào)用在不能立刻得到結(jié)果之前,該調(diào)用不會(huì)阻塞當(dāng)前線程。

?

為什么JavaScript是單線程?

JavaScript是單線程,程序按照順序排列,前面的必須處理好,后面的才會(huì)執(zhí)行。JavaScript的設(shè)計(jì)初衷是作為瀏覽器腳本語言,主要是簡單用戶交互、操作DOM等,所以這門語言要圍繞單線程來設(shè)計(jì),否則出現(xiàn)復(fù)雜的同步問題。

由于JavaScript是單線程的,對(duì)于耗時(shí)的或者時(shí)間不確定的操作,我們可以使用異步編程,因?yàn)?span style="font-family:'宋體';">異步可以實(shí)現(xiàn)非阻塞操作。當(dāng)然也可以用HTML5標(biāo)準(zhǔn)的Web Worker本文不作討論,詳細(xì)參考MDN文檔:點(diǎn)擊這里

既然js是單線程執(zhí)行的,那誰去輪詢大的任務(wù)隊(duì)列?這不矛盾了嗎?

?

Js的單線程與異步矛盾嗎?

不矛盾!!!首先記住這句話:Js執(zhí)行是單線程,但瀏覽器是多線程

1)JS的單線程

一個(gè)瀏覽器進(jìn)程中只有一個(gè)JS的執(zhí)行線程,同一時(shí)刻內(nèi)只會(huì)有一段代碼在執(zhí)行

?

2)瀏覽器多線程

查閱資料,有些文章也說是模塊,本文就以瀏覽器多線程來說,它常駐線程:

渲染引擎線程:負(fù)責(zé)頁面的渲染

JS引擎線程:負(fù)責(zé)JS的解析和執(zhí)行(本文說的主線程就指js引擎線程)

定時(shí)器觸發(fā)線程:處理定時(shí)事件,比如setTimeout, setInterval

事件觸發(fā)線程:處理DOM事件

異步http請(qǐng)求線程:處理http請(qǐng)求

? ? ? ? ......

瀏覽器Js使用場景,瀏覽器本身是典型的 GUI 工作線程(GUI 工作線程在絕大多數(shù)系統(tǒng)中都實(shí)現(xiàn)為事件處理,避免阻塞交互)。故瀏覽器是事件驅(qū)動(dòng)的(Event driven),瀏覽器中很多行為是異步,會(huì)創(chuàng)建事件并放入任務(wù)隊(duì)列中。

由于Javascript 是單線程,它需要借助異步完成耗時(shí)或者時(shí)間不確定的操作,這些操作由瀏覽器的其它線程執(zhí)行,這形成了異步事件驅(qū)動(dòng)。異步事件驅(qū)動(dòng)往往由瀏覽器的兩個(gè)或以上常駐線程共同完成的例如ajax異步請(qǐng)求是由JS執(zhí)行線程和異步http請(qǐng)求線程事件觸發(fā)線程共同完成的

?

事件循環(huán)機(jī)制(Event Loop)

相關(guān)概念

函數(shù)調(diào)用形成一個(gè)棧幀。

1 function foo(b) { 2 let a = 10; 3 return a + b + 11; 4 } 5 6 function bar(x) { 7 let y = 3; 8 return foo(x * y); 9 } 10 11 console.log(bar(7));

當(dāng)調(diào)用 bar?時(shí),創(chuàng)建了第一個(gè)幀 ,幀中包含了 bar?的參數(shù)和局部變量。

當(dāng) bar?調(diào)用 foo?時(shí),第二個(gè)幀就被創(chuàng)建,并被壓到第一個(gè)幀之上,幀中包含了 foo 的參數(shù)和局部變量。

當(dāng) foo 返回時(shí),最上層的幀就被彈出棧(剩下 bar?函數(shù)的調(diào)用幀 )。

當(dāng) bar?返回的時(shí)候,棧就空了。

對(duì)象被分配在一個(gè)堆中,一個(gè)用以表示一個(gè)內(nèi)存中大的未被組織的區(qū)域。

每一個(gè)線程只有一個(gè)棧,每一個(gè)程序只有一個(gè)堆。

隊(duì)列

一個(gè) JavaScript 運(yùn)行時(shí)包含了一個(gè)待處理的消息隊(duì)列。每一個(gè)消息都與一個(gè)函數(shù)相關(guān)聯(lián)。

當(dāng)棧為空時(shí),從隊(duì)列中取出一個(gè)消息進(jìn)行處理。這個(gè)處理過程包含了調(diào)用與這個(gè)消息相關(guān)聯(lián)的函數(shù)。

當(dāng)棧再次為空的時(shí)候,也就意味著消息處理結(jié)束。

?

任務(wù)隊(duì)列消息隊(duì)列

任務(wù)隊(duì)列是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),當(dāng)主線程執(zhí)行棧一清空,任務(wù)隊(duì)列的回調(diào)函數(shù)就自動(dòng)進(jìn)入主線程。任務(wù)分成兩種

1同步任務(wù)在主線程上排隊(duì)執(zhí)行的任務(wù)只有執(zhí)行完當(dāng)前任務(wù),才能執(zhí)行后一個(gè)任務(wù)

2異步任務(wù):該任務(wù)不進(jìn)入主線程、而進(jìn)入任務(wù)隊(duì)列。當(dāng)執(zhí)行棧清空后,才去執(zhí)行任務(wù)隊(duì)列中的任務(wù)。

?

異步執(zhí)行的運(yùn)行機(jī)制

由于JavaScript只能一次執(zhí)行一段代碼(由于其單線程性質(zhì)),這些代碼塊中的每一個(gè)都“阻止”其他異步事件的進(jìn)度。這意味著當(dāng)異步事件發(fā)生時(shí)(如鼠標(biāo)點(diǎn)擊,定時(shí)器觸發(fā)或XMLHttpRequest完成),它將排隊(duì)等待稍后執(zhí)行(這種排隊(duì)實(shí)際發(fā)生的確定會(huì)因?yàn)g覽器到瀏覽器而異)。

1所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧

2、當(dāng)遇到異步任務(wù)時(shí)(IO設(shè)備操作等,就在任務(wù)隊(duì)列中添加一個(gè)事件,這個(gè)事件對(duì)應(yīng)著該異步任務(wù)的回調(diào)函數(shù)。

3執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列,進(jìn)入執(zhí)行棧,開始執(zhí)行。

4主線程不斷重復(fù)第三步。這就形成了事件循環(huán)

結(jié)論:Javascript的事件分同步任務(wù)和異步任務(wù),遇到同步任務(wù)就放在執(zhí)行棧中執(zhí)行,而碰到異步任務(wù)就放到任務(wù)隊(duì)列之中,等到執(zhí)行棧執(zhí)行完畢之后再去執(zhí)行任務(wù)隊(duì)列之中的事件。

?

事件和回調(diào)函數(shù)的概念必要說明

  • 工作線程:是本文對(duì)除了js引擎線程之外的其它線程的統(tǒng)稱
  • 回調(diào)函數(shù):在一個(gè)函數(shù)中調(diào)用另外一個(gè)函數(shù)。這里指異步場景下為了非阻塞那些被主線程掛起來的代碼。
  • ?主線程讀取任務(wù)隊(duì)列,就是讀取里面有哪些事件,執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。

工作線程完成一項(xiàng)任務(wù),就任務(wù)隊(duì)列中添加一個(gè)事件。這里的完成任務(wù)是指完成操作(clickmousetouchajax的數(shù)據(jù)完全請(qǐng)求回來......),并非指執(zhí)行它的回調(diào)函數(shù)

a.onclick = function () {console.log("roro") }

如上段代碼,僅是操作了click,但并沒有執(zhí)行回調(diào)函數(shù)打印roro

?

事件循環(huán)

事件循環(huán)是:主線程重復(fù)從任務(wù)隊(duì)列中取消息(事件),執(zhí)行對(duì)應(yīng)回調(diào)函數(shù)的過程

?

?

上圖中,主線程運(yùn)行的時(shí)候,產(chǎn)生堆(heap)和棧(stack),棧中的代碼調(diào)用各種外部API,它們?cè)谌蝿?wù)隊(duì)列中加入各種事件(clickloaddone)。只要執(zhí)行引擎棧棧中的代碼執(zhí)行完畢,主線程就會(huì)去讀取任務(wù)隊(duì)列,依次執(zhí)行那些事件所對(duì)應(yīng)的回調(diào)函數(shù)。

?

定時(shí)器

首先參考這篇外國人的文章:how-javascript-timers-work,定時(shí)器的執(zhí)行原理及細(xì)節(jié)。

setTimeout()怎么執(zhí)行?

setTimeout(function () {console.log('a'); }, 5000)

? ? ? ?Javascript執(zhí)行引擎主線程運(yùn)行的時(shí)候,產(chǎn)生堆和棧。程序中代碼依次進(jìn)入棧中等待執(zhí)行,當(dāng)調(diào)用setTimeout()方法時(shí),瀏覽器的定時(shí)器線程下處理延時(shí)方法,當(dāng)setTimeout方法執(zhí)行5秒后到達(dá)觸發(fā)條件方法被添加到用于回調(diào)的任務(wù)隊(duì)列

? ? ? ?當(dāng)執(zhí)行引擎的執(zhí)行棧為空,執(zhí)行引擎開始輪詢檢查任務(wù)隊(duì)列是否有任務(wù)需要被執(zhí)行,當(dāng)檢查到已經(jīng)符合執(zhí)行條件的延時(shí)方法時(shí),將延時(shí)方法console.log('a')壓入執(zhí)行棧引擎發(fā)現(xiàn)調(diào)用了log()方法,于是又將log()方法入棧。然后對(duì)執(zhí)行棧依次出棧執(zhí)行,輸出‘a(chǎn)’,清空?qǐng)?zhí)行棧,整個(gè)執(zhí)行完畢。

?

setTimeout(fn,0)是立即執(zhí)行嗎?

javascript權(quán)威指南中:當(dāng)setTimeout的延遲時(shí)間設(shè)置為0的時(shí)候,回調(diào)函數(shù)不會(huì)馬上執(zhí)行,而是進(jìn)入?事件隊(duì)列。

?

btn.onclick = function () {setTimeout(function () {console.log('a')}, 0); }

?

setTimeout(fn,0)的含義是:指定某個(gè)任務(wù)在主線程的空閑時(shí)間,盡可能早執(zhí)行。它被添加進(jìn)任務(wù)隊(duì)列,因此要等到同步任務(wù)和任務(wù)隊(duì)列前一個(gè)事件都處理完,才會(huì)執(zhí)行。

HTML5標(biāo)準(zhǔn)規(guī)定了setTimeout()的第二個(gè)參數(shù)的最小值不得低于4ms,如果低于這個(gè)值,就會(huì)自動(dòng)增加。老版本的瀏覽器允許最短間隔設(shè)為10ms詳細(xì)參考MDN文檔:最小延遲和超時(shí)嵌套

所以setTimeout(fn,0)并不是立即執(zhí)行。假若你想實(shí)現(xiàn)0ms 的timeout可以用?window.postMessage(),本文不作討論。

?

兩道經(jīng)典的面試題:

一)以下代碼輸出什么?

function foo() {console.log('a')setTimeout(function () {console.log('b');}, 500) }for (let i = 0; i < 10000; i++) {foo() }

執(zhí)行結(jié)果首先全部輸出a,中間等待500ms,然后全部輸出b。上圖是個(gè)人理解,不恰當(dāng)?shù)牡胤秸?qǐng)指出!

?

二)ajax異步請(qǐng)求是否真的異步?

1、JS的執(zhí)行線程(主線程)發(fā)起異步請(qǐng)求瀏覽器會(huì)開一條新的HTTP請(qǐng)求線程來執(zhí)行請(qǐng)求,繼續(xù)執(zhí)行中剩下的任務(wù),

2、在新線程(HTTP請(qǐng)求線程)中,在執(zhí)行請(qǐng)求的同時(shí),瀏覽器會(huì)正常處理其他任務(wù)的執(zhí)行。

3在未來的某一時(shí)刻當(dāng)數(shù)據(jù)完全請(qǐng)求回來以后事件觸發(fā)線程監(jiān)視到之前發(fā)起的HTTP請(qǐng)求已完成,會(huì)將指定的回調(diào)函數(shù)放入任務(wù)隊(duì)列中。

4當(dāng)瀏覽器執(zhí)行棧空閑時(shí),去掃描任務(wù)隊(duì)列中的回調(diào)函數(shù),依次壓入執(zhí)行棧中處理。

所以:ajax請(qǐng)求是異步由瀏覽器新開一個(gè)線程請(qǐng)求,事件回調(diào)的時(shí)候放入Event loop任務(wù)隊(duì)列等候處理。詳細(xì)的例子,可以參考MDN文檔對(duì)ajax的描述:同步和異步

?

誤解:事件循環(huán)類似棧或隊(duì)列

這里順帶提一下:事件循環(huán)雖然涉及到類似隊(duì)列的結(jié)構(gòu),并不是采用棧的方式處理任務(wù)。事件循環(huán)作為一個(gè)進(jìn)程被劃分為多個(gè)階段,每個(gè)階段處理一些特定任務(wù),各階段輪詢調(diào)度。這些階段可以是定時(shí)器處理,dom事件處理,ajax異步處理......

?

結(jié)語

JavaScript引擎只有一個(gè)線程,強(qiáng)制異步事件排隊(duì)等待執(zhí)行,Javascript語言的事件循環(huán),是瀏覽器的處理和行為。另外,本文是我個(gè)人的學(xué)習(xí)筆記,通篇結(jié)合個(gè)人的理解,在某些地方表述不嚴(yán)謹(jǐn),如有錯(cuò)誤,希望指出。

轉(zhuǎn)載于:https://www.cnblogs.com/LIUYANZUO/p/7353547.html

總結(jié)

以上是生活随笔為你收集整理的从Javascript单线程谈Event Loop的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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