最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

簡(jiǎn)單聊聊JavaScript中的事件循環(huán)

 更新時(shí)間:2023年02月09日 09:40:13   作者:mick  
js的事件循環(huán)(event-loop)是我們前端學(xué)習(xí)中非常重要的一部分,也是面試中經(jīng)常被問(wèn)到的點(diǎn),這篇文章我們就來(lái)給大家著重講解一下吧

為什么js是單線程的

我們首先要考慮下js作為瀏覽器腳本語(yǔ)言,主要用途就是和用戶互動(dòng)和操作DOM。比如js同時(shí)有兩個(gè)線程,兩個(gè)線程同時(shí)操作同一個(gè)DOM,比如一個(gè)給DOM添加內(nèi)容,一個(gè)移除DOM,那到底該聽(tīng)誰(shuí)的呢?所以這就決定了它只能是單線程,否則就會(huì)出現(xiàn)一些奇怪的問(wèn)題。

瀏覽器

我們每打開(kāi)一個(gè)tab頁(yè)就會(huì)產(chǎn)生一個(gè)進(jìn)程

瀏覽器都包含哪些進(jìn)程呢

瀏覽器進(jìn)程

  • 瀏覽器的主進(jìn)程(負(fù)責(zé)協(xié)調(diào)、主控),該進(jìn)程只有一個(gè)
  • 負(fù)責(zé)瀏覽器界面顯示,用戶交互。如前進(jìn),后退等
  • 負(fù)責(zé)各個(gè)頁(yè)面的管理,創(chuàng)建和銷(xiāo)毀其他進(jìn)程
  • 將渲染(renderer)進(jìn)程得到的內(nèi)存中的Bitmap(位圖),繪制到用戶界面上
  • 網(wǎng)絡(luò)資源的管理,下載等

第三方插件進(jìn)程

每種類(lèi)型的插件對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)使用該插件時(shí)才創(chuàng)建。因插件易崩潰,所以需要通過(guò)插件進(jìn)程來(lái)隔離,以保證插件進(jìn)程崩潰不會(huì)對(duì)瀏覽器和頁(yè)面造成影響

GPU進(jìn)程

該進(jìn)程只有一個(gè),用于3D繪制等

渲染進(jìn)程

  • 通常所說(shuō)的瀏覽器內(nèi)核(Renderer進(jìn)程,內(nèi)部是多線程)
  • 每個(gè)Tab頁(yè)面都有一個(gè)渲染進(jìn)程,互不影響
  • 主要作用為頁(yè)面渲染,腳本執(zhí)行,事件處理等

網(wǎng)絡(luò)進(jìn)程

主要負(fù)責(zé)頁(yè)面的網(wǎng)絡(luò)資源加載。

如果瀏覽器是單進(jìn)程,那么當(dāng)一個(gè)tab頁(yè)面崩潰了,就會(huì)影響到整個(gè)瀏覽器。同時(shí)如果插件崩潰了也會(huì)影響整個(gè)瀏覽器。瀏覽器進(jìn)程有很多,每個(gè)進(jìn)程又有很多的線程,都會(huì)占用內(nèi)存。進(jìn)程之間的內(nèi)容相互隔離,這是為了保護(hù)操作系統(tǒng)中進(jìn)行互不干擾的技術(shù),每一個(gè)進(jìn)程只能訪問(wèn)自己占有的數(shù)據(jù),也就避免了進(jìn)程A寫(xiě)入數(shù)據(jù)到進(jìn)程B的情況。因?yàn)檫M(jìn)程之間的數(shù)據(jù)是嚴(yán)格隔離的,所以一個(gè)進(jìn)程如果崩潰了,或者掛起了,是不會(huì)影響到其他進(jìn)程的。

渲染進(jìn)程

頁(yè)面的渲染、js的執(zhí)行、事件的循環(huán)、都在渲染進(jìn)程中執(zhí)行,所以重點(diǎn)看下渲染進(jìn)程。渲染進(jìn)程是多線程的,下面看下比較常用的幾個(gè)線程

GUI線程

負(fù)責(zé)渲染瀏覽器界面,解析HTML,CSS,構(gòu)建DOM樹(shù)和RenderObject樹(shù),布局和繪制等。

當(dāng)修改了一些元素的顏色或者背景色,頁(yè)面就會(huì)重繪(Repaint)

當(dāng)修改元素的尺寸,頁(yè)面就是重排也叫回流(Reflow)

當(dāng)頁(yè)面需要重繪和重排的時(shí)候GUI線程執(zhí)行,繪制頁(yè)面

重繪和重排的成本比較高,盡量避免重繪和重排

GUI線程和JS引擎線程是互斥的

  • 當(dāng)JS引擎執(zhí)行時(shí),GUI線程會(huì)被掛起
  • GUI更新會(huì)被保存在一個(gè)隊(duì)列中,等JS引擎空閑的時(shí)候立即被執(zhí)行。

JS引擎線程

JS引擎線程就是JS內(nèi)核,負(fù)責(zé)處理JavaScript腳本程序(例如V8引擎)

JS引擎線程負(fù)責(zé)解析JavaScript腳本,運(yùn)行代碼

JS引擎一直等待任務(wù)隊(duì)列的到來(lái),然后加以處理

  • 瀏覽器同時(shí)只能有一個(gè)JS引擎線程在運(yùn)行JS程序,所以JS是單線程運(yùn)行的
  • 一個(gè)Tab頁(yè)在Renderer進(jìn)程中無(wú)論什么時(shí)候都只有一個(gè)JS線程在運(yùn)行JS程序

GUI線程和JS引擎是互斥的,JS引擎線程會(huì)阻塞GUI渲染線程

如果JS執(zhí)行的時(shí)間過(guò)長(zhǎng),這樣就會(huì)造成頁(yè)面的渲染不連貫,導(dǎo)致頁(yè)面渲染加載阻塞。

事件觸發(fā)線程

  • 屬于瀏覽器而不是JS引擎,用來(lái)控制事件循環(huán),并且管理著一個(gè)事件隊(duì)列(task queue)
  • 當(dāng)JS引擎執(zhí)行事件綁定和一些異步操作如SetTimeOut時(shí),也可能是瀏覽器內(nèi)核的其他線程,如鼠標(biāo)點(diǎn)擊、Ajax異步請(qǐng)求等,會(huì)走事件觸發(fā)線程將對(duì)應(yīng)的事件添加到對(duì)應(yīng)的線程中(比如定時(shí)器操作,便把定時(shí)器事件添加到定時(shí)器線程),等異步事件有了結(jié)果,便把他們的回調(diào)操作添加到事件隊(duì)列,等待js引擎線程空閑時(shí)來(lái)處理。
  • 當(dāng)對(duì)應(yīng)的事件符合觸發(fā)條件被觸發(fā)時(shí),該線程會(huì)把事件添加到待處理隊(duì)列的隊(duì)尾,等待JS引擎的處理
  • JS是單線程,所以這些待處理隊(duì)列中的事件都會(huì)排隊(duì)等待JS引擎處理

定時(shí)觸發(fā)器線程

  • setInterval與setTimeout所在線程
  • 瀏覽器定時(shí)計(jì)數(shù)器并不是由JS引擎計(jì)數(shù)的(因?yàn)镴S引擎是單線程的,如果處于阻塞線程狀態(tài)就會(huì)影響計(jì)時(shí)的準(zhǔn)確性)
  • 通過(guò)單獨(dú)線程來(lái)計(jì)時(shí)并觸發(fā)定時(shí)(計(jì)時(shí)完畢后,添加到事件觸發(fā)線程的事件隊(duì)列中,等待JS引擎空閑后執(zhí)行)
  • 注意,W3C在HTML標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求setTimeout中低于4ms的時(shí)間間隔算為4ms。

異步HTTP請(qǐng)求線程

  • 在XMLHttpRequest在連接后是通過(guò)瀏覽器新開(kāi)的一個(gè)線程請(qǐng)求
  • 將檢測(cè)到狀態(tài)變更時(shí),如果設(shè)置有回調(diào)函數(shù),異步線程就會(huì)產(chǎn)生狀態(tài)變更事件,將這個(gè)回調(diào)再放入事件隊(duì)列中再由JS引擎執(zhí)行
  • 簡(jiǎn)單說(shuō)就是當(dāng)執(zhí)行到一個(gè)http異步請(qǐng)求時(shí),就把異步請(qǐng)求事件添加到異步請(qǐng)求線程,等收到響應(yīng)(準(zhǔn)確來(lái)說(shuō)應(yīng)該是http狀態(tài)變化),再把回調(diào)函數(shù)添加到事件隊(duì)列,等待js引擎線程來(lái)執(zhí)行

下面就來(lái)談?wù)勎覀兊闹仡^戲

事件循環(huán)

  • JS被分為同步任務(wù)和異步任務(wù)。
  • 同步任務(wù)在主線程(JS引擎線程)上執(zhí)行,形成一個(gè)執(zhí)行棧。
  • 除了主線程之外,事件觸發(fā)線程管理這一個(gè)任務(wù)隊(duì)列,只要異步任務(wù)有了結(jié)果,就會(huì)在任務(wù)隊(duì)列中放入異步任務(wù)的回調(diào)。
  • 當(dāng)執(zhí)行棧中所有的同步任務(wù)執(zhí)行完畢后,就會(huì)讀取任務(wù)隊(duì)列,將可運(yùn)行的異步任務(wù)(任務(wù)隊(duì)列中的事件回調(diào),只要任務(wù)隊(duì)列中有事件回調(diào),就說(shuō)明可以執(zhí)行)添加到執(zhí)行棧中,開(kāi)始執(zhí)行。 我們畫(huà)個(gè)圖來(lái)表示一下

let setTimeoutCallBack = function() {
  console.log('我是定時(shí)器回調(diào)');
};
let httpCallback = function() {
  console.log('我是http請(qǐng)求回調(diào)');
}

// 同步任務(wù)
console.log('我是同步任務(wù)1');

// 異步定時(shí)任務(wù)
setTimeout(setTimeoutCallBack,1000);

// 異步http請(qǐng)求任務(wù)
ajax.get('/info',httpCallback);

// 同步任務(wù)
console.log('我是同步任務(wù)2');

我們來(lái)看下這段代碼。解析一下執(zhí)行過(guò)程

  • js會(huì)從上到下依次執(zhí)行,可以先理解為這段代碼的執(zhí)行環(huán)境就是主線程,也就是當(dāng)前執(zhí)行棧
  • 首先 執(zhí)行console.log('我是同步任務(wù)1');
  • 然后執(zhí)行到setTimeout時(shí)候,會(huì)交給定時(shí)器線程,并告訴定時(shí)器線程在1s后將setTimeoutCallBack回調(diào)交給事件觸發(fā)線程,1s后事件觸發(fā)線程把這個(gè)回調(diào)添加到了任務(wù)隊(duì)列中等待執(zhí)行
  • 接著執(zhí)行ajax,會(huì)交給異步HTTP請(qǐng)求線程發(fā)送網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求成功后,將回調(diào)httpCallback交給事件觸發(fā)線程并放入任務(wù)隊(duì)列中等待執(zhí)行。
  • 接著執(zhí)行console.log('我是同步任務(wù)2');
  • 此時(shí)主線程執(zhí)行棧執(zhí)行完畢,js引擎線程已經(jīng)空閑,開(kāi)始詢(xún)問(wèn)事件觸發(fā)線程的任務(wù)隊(duì)列中是否有需要執(zhí)行的回調(diào),如果有則將任務(wù)隊(duì)列中的回調(diào)事件加入執(zhí)行棧中,開(kāi)始執(zhí)行,如果任務(wù)隊(duì)列中沒(méi)有需要執(zhí)行的回調(diào),js引擎會(huì)不斷的發(fā)起詢(xún)問(wèn),直到有為止。

瀏覽器上的所有線程是的行為都很單一。

  • 定時(shí)觸發(fā)線程只管理定時(shí)器并只關(guān)心定時(shí)不關(guān)注結(jié)果,定時(shí)結(jié)束后就把回調(diào)交給事件觸發(fā)線程
  • 異步HTTP請(qǐng)求線程只關(guān)心http請(qǐng)求不關(guān)心結(jié)果,請(qǐng)求結(jié)束后就把回調(diào)交給時(shí)間觸發(fā)線程
  • 事件觸發(fā)線程只將異步回調(diào)加入事件隊(duì)列
  • JS引擎線程則執(zhí)行執(zhí)行棧中的事件,執(zhí)行棧中的代碼執(zhí)行完畢后,就會(huì)詢(xún)問(wèn)事件觸發(fā)線程的事件隊(duì)列是否有回調(diào)需要執(zhí)行,然后把事件隊(duì)列中的事件添加到執(zhí)行棧中執(zhí)行,這樣的反反復(fù)復(fù)的行為我們稱(chēng)為事件循環(huán)

了解了事件循環(huán)下面看下宏任務(wù)和微任務(wù)

宏任務(wù) 微任務(wù)

宏任務(wù)

  • 渲染事件(如解析 DOM、計(jì)算布局、繪制)
  • 用戶交互事件(如鼠標(biāo)點(diǎn)擊、滾動(dòng)頁(yè)面、放大縮小等)
  • JavaScript 腳本執(zhí)行事件
  • 網(wǎng)絡(luò)請(qǐng)求完成、文件讀寫(xiě)完成事件

為了協(xié)調(diào)這些任務(wù)能夠有序的在主線程上執(zhí)行,頁(yè)面進(jìn)行引入了消息隊(duì)列和事件循環(huán)機(jī)制,渲染進(jìn)程內(nèi)部會(huì)維護(hù)多個(gè)消息隊(duì)列,主線程不斷的從這些任務(wù)隊(duì)列中取出任務(wù)并執(zhí)行任務(wù)。我們把這些消息隊(duì)列中的任務(wù)稱(chēng)為宏任務(wù)

常見(jiàn)的宏任務(wù):

  • 主代碼塊
  • setTimeOut
  • setInterval
  • setImmediate -- node
  • requestAnimationFrame -- 瀏覽器 JS引擎線程和GUI渲染線程是互斥的,瀏覽器為了能夠使宏任務(wù)和DOM任務(wù)有序的進(jìn)行,會(huì)在一個(gè)宏任務(wù)結(jié)束后,在一個(gè)宏任務(wù)執(zhí)行前,GUI渲染線程開(kāi)始工作,對(duì)頁(yè)面進(jìn)行渲染

微任務(wù)

微任務(wù)就是一個(gè)需要異步執(zhí)行的函數(shù),執(zhí)行時(shí)機(jī)是在主函數(shù)執(zhí)行結(jié)束之后、當(dāng)前宏任務(wù)結(jié)束后之前。

異步回調(diào)有兩種方式

  • 把異步回調(diào)函數(shù)封裝成一個(gè)宏任務(wù),添加到消息隊(duì)列中,當(dāng)循環(huán)系統(tǒng)執(zhí)行到該任務(wù)的時(shí)候執(zhí)行回調(diào)函數(shù)
  • 執(zhí)行時(shí)機(jī)是在主函數(shù)執(zhí)行結(jié)束之后、當(dāng)前宏任務(wù)結(jié)束之前執(zhí)行回調(diào)函數(shù),這通常都是以微任務(wù)的形式體現(xiàn)的

我們知道宏任務(wù)結(jié)束后,會(huì)執(zhí)行渲染,然后執(zhí)行下一次宏任務(wù),微任務(wù)可以理解為當(dāng)前宏任務(wù)執(zhí)行后立即執(zhí)行的任務(wù)。

常見(jiàn)的微任務(wù):

  • process.nextTick()--node
  • Promise.then()
  • catch
  • finally
  • Object.observe
  • MutationObserver

當(dāng)執(zhí)行完一個(gè)宏任務(wù)之后,會(huì)立即執(zhí)行期間所產(chǎn)生的所有微任務(wù),然后執(zhí)行渲染

宏任務(wù)微任務(wù)的執(zhí)行流程

瀏覽器首先會(huì)執(zhí)行一個(gè)宏任務(wù),然后執(zhí)行當(dāng)前執(zhí)行棧所產(chǎn)生的微任務(wù),然后再渲染頁(yè)面,然后再執(zhí)行下一個(gè)宏任務(wù)

面試題

function test() {
  console.log(1)
  setTimeout(function () { 	// timer1
    console.log(2)
  }, 1000)
}

test();

setTimeout(function () { 		// timer2
  console.log(3)
})

new Promise(function (resolve) {
  console.log(4)
  setTimeout(function () { 	// timer3
    console.log(5)
  }, 100)
  resolve()
}).then(function () {
  setTimeout(function () { 	// timer4
    console.log(6)
  }, 0)
  console.log(7)
})

console.log(8)

下面我們來(lái)分析一下整體的流程

首先應(yīng)該找到同步任務(wù)先執(zhí)行

  • 當(dāng)test()調(diào)用的時(shí)候 console.log(1)會(huì)先執(zhí)行,打印1。而setTimeout(我們記作timer1)作為宏任務(wù)加入宏任務(wù)隊(duì)列
  • test下面的setTimtout(我們記作timer2)作為宏任務(wù)加入宏任務(wù)隊(duì)列
  • new Promise()的executer中中也會(huì)當(dāng)做同步任務(wù)執(zhí)行 所以console.log(4)打印4。而setTimeout(我們記作timer3)作為宏任務(wù)加入宏任務(wù)隊(duì)列
  • 接著promise.then()作為微任務(wù)加入微任務(wù)隊(duì)列
  • 最后console.log(8)作為同步任務(wù)執(zhí)行,打印8

我們?cè)倏串惒饺蝿?wù)

  • 我們當(dāng)前的執(zhí)行棧本身就是宏任務(wù),宏任務(wù)執(zhí)行完了之后應(yīng)該立即執(zhí)行微任務(wù),這里的微任務(wù)只有Promise.then(),而setTimeout(我們記作timer4)作為宏任務(wù)加入宏任務(wù)隊(duì)列,然后執(zhí)行console.log(7)打印7
  • 微任務(wù)執(zhí)行完畢之后,要執(zhí)行GUI渲染,我們代碼中沒(méi)有
  • 執(zhí)行宏任務(wù)隊(duì)列,此時(shí)宏任務(wù)隊(duì)列里面有 timer1、timer2、timer3、timer4
  • 按照定時(shí)時(shí)間,可以排列為:timer2、timer4、timer3、timer1依次拿出放入執(zhí)行棧末尾執(zhí)行
  • 執(zhí)行timer2,console.log(3)作為同步任務(wù)打印3,然后檢查有沒(méi)有微任務(wù)和GUI渲染
  • 執(zhí)行timer4,console.log(6)作為同步任務(wù)打印6,然后檢查有沒(méi)有微任務(wù)和GUI渲染
  • 執(zhí)行timer3,console.log(5)作為同步任務(wù)打印5,然后檢查有沒(méi)有微任務(wù)和GUI渲染
  • 執(zhí)行timer1,console.log(2)作為同步任務(wù)打印2,然后檢查有沒(méi)有微任務(wù)和GUI渲染 所以最終結(jié)果為:1、4、8、7、3、6、5、2

到此這篇關(guān)于簡(jiǎn)單聊聊JavaScript中的事件循環(huán)的文章就介紹到這了,更多相關(guān)JavaScript事件循環(huán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

达日县| 连城县| 乐东| 泊头市| 咸丰县| 两当县| 百色市| 阜平县| 施秉县| 崇州市| 黄骅市| 宝清县| 孟州市| 松潘县| 紫云| 古丈县| 白水县| 鄂伦春自治旗| 墨竹工卡县| 郎溪县| 罗平县| 景东| 广灵县| 陇南市| 察隅县| 贡山| 唐山市| 曲周县| 潞城市| 湘西| 张家港市| 浙江省| 武邑县| 呼图壁县| 东丽区| 房山区| 太白县| 忻城县| 江陵县| 芜湖县| 盖州市|