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

JS進(jìn)階之從多線程到Event?Loop全面梳理

 更新時(shí)間:2023年05月23日 10:53:26   作者:云中橋  
JS是通過(guò)事件隊(duì)列(Event?Loop)的方式來(lái)實(shí)現(xiàn)異步回調(diào)的,但對(duì)很多初學(xué)JS的人來(lái)說(shuō),根本搞不清楚單線程的JS為什么擁有異步的能力,所以本文將從進(jìn)程、線程的角度來(lái)解釋這個(gè)問(wèn)題

引子

幾乎在每一本JS相關(guān)的書籍中,都會(huì)說(shuō)JS是單線程的,JS是通過(guò)事件隊(duì)列(Event Loop)的方式來(lái)實(shí)現(xiàn)異步回調(diào)的。 對(duì)很多初學(xué)JS的人來(lái)說(shuō),根本搞不清楚單線程的JS為什么擁有異步的能力,所以,我試圖從進(jìn)程、線程的角度來(lái)解釋這個(gè)問(wèn)題。

CPU

計(jì)算機(jī)的核心是CPU,它承擔(dān)了所有的計(jì)算任務(wù)。

它就像一座工廠,時(shí)刻在運(yùn)行。

進(jìn)程

假定工廠的電力有限,一次只能供給一個(gè)車間使用。 也就是說(shuō),一個(gè)車間開工的時(shí)候,其他車間都必須停工。 背后的含義就是,單個(gè)CPU一次只能運(yùn)行一個(gè)任務(wù)。

進(jìn)程就好比工廠的車間,它代表CPU所能處理的單個(gè)任務(wù)。 進(jìn)程之間相互獨(dú)立,任一時(shí)刻,CPU總是運(yùn)行一個(gè)進(jìn)程,其他進(jìn)程處于非運(yùn)行狀態(tài)。 CPU使用時(shí)間片輪轉(zhuǎn)進(jìn)度算法來(lái)實(shí)現(xiàn)同時(shí)運(yùn)行多個(gè)進(jìn)程。

線程

一個(gè)車間里,可以有很多工人,共享車間所有的資源,他們協(xié)同完成一個(gè)任務(wù)。

線程就好比車間里的工人,一個(gè)進(jìn)程可以包括多個(gè)線程,多個(gè)線程共享進(jìn)程資源。

CPU、進(jìn)程、線程之間的關(guān)系

從上文我們已經(jīng)簡(jiǎn)單了解了CPU、進(jìn)程、線程,簡(jiǎn)單匯總一下。

  • 進(jìn)程是cpu資源分配的最小單位(是能擁有資源和獨(dú)立運(yùn)行的最小單位)
  • 線程是cpu調(diào)度的最小單位(線程是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位,一個(gè)進(jìn)程中可以有多個(gè)線程)
  • 不同進(jìn)程之間也可以通信,不過(guò)代價(jià)較大
  • 單線程與多線程,都是指在一個(gè)進(jìn)程內(nèi)的單和多

瀏覽器是多進(jìn)程的

我們已經(jīng)知道了CPU、進(jìn)程、線程之間的關(guān)系,對(duì)于計(jì)算機(jī)來(lái)說(shuō),每一個(gè)應(yīng)用程序都是一個(gè)進(jìn)程, 而每一個(gè)應(yīng)用程序都會(huì)分別有很多的功能模塊,這些功能模塊實(shí)際上是通過(guò)子進(jìn)程來(lái)實(shí)現(xiàn)的。 對(duì)于這種子進(jìn)程的擴(kuò)展方式,我們可以稱這個(gè)應(yīng)用程序是多進(jìn)程的。

而對(duì)于瀏覽器來(lái)說(shuō),瀏覽器就是多進(jìn)程的,我在Chrome瀏覽器中打開了多個(gè)tab,然后打開windows控制管理器:

如上圖,我們可以看到一個(gè)Chrome瀏覽器啟動(dòng)了好多個(gè)進(jìn)程。

總結(jié)一下:

  • 瀏覽器是多進(jìn)程的
  • 每一個(gè)Tab頁(yè),就是一個(gè)獨(dú)立的進(jìn)程

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

主進(jìn)程

  • 協(xié)調(diào)控制其他子進(jìn)程(創(chuàng)建、銷毀)
  • 瀏覽器界面顯示,用戶交互,前進(jìn)、后退、收藏
  • 將渲染進(jìn)程得到的內(nèi)存中的Bitmap,繪制到用戶界面上
  • 處理不可見(jiàn)操作,網(wǎng)絡(luò)請(qǐng)求,文件訪問(wèn)等

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

每種類型的插件對(duì)應(yīng)一個(gè)進(jìn)程,僅當(dāng)使用該插件時(shí)才創(chuàng)建

GPU進(jìn)程

用于3D繪制等

渲染進(jìn)程,就是我們說(shuō)的瀏覽器內(nèi)核

  • 負(fù)責(zé)頁(yè)面渲染,腳本執(zhí)行,事件處理等
  • 每個(gè)tab頁(yè)一個(gè)渲染進(jìn)程

那么瀏覽器中包含了這么多的進(jìn)程,那么對(duì)于普通的前端操作來(lái)說(shuō),最重要的是什么呢?

答案是渲染進(jìn)程,也就是我們常說(shuō)的瀏覽器內(nèi)核

瀏覽器內(nèi)核(渲染進(jìn)程)

從前文我們得知,進(jìn)程和線程是一對(duì)多的關(guān)系,也就是說(shuō)一個(gè)進(jìn)程包含了多條線程。

而對(duì)于渲染進(jìn)程來(lái)說(shuō),它當(dāng)然也是多線程的了,接下來(lái)我們來(lái)看一下渲染進(jìn)程包含哪些線程。

GUI渲染線程

  • 負(fù)責(zé)渲染頁(yè)面,布局和繪制
  • 頁(yè)面需要重繪和回流時(shí),該線程就會(huì)執(zhí)行
  • 與js引擎線程互斥,防止渲染結(jié)果不可預(yù)期

JS引擎線程

  • 負(fù)責(zé)處理解析和執(zhí)行javascript腳本程序
  • 只有一個(gè)JS引擎線程(單線程)
  • 與GUI渲染線程互斥,防止渲染結(jié)果不可預(yù)期

事件觸發(fā)線程

  • 用來(lái)控制事件循環(huán)(鼠標(biāo)點(diǎn)擊、setTimeout、ajax等)
  • 當(dāng)事件滿足觸發(fā)條件時(shí),將事件放入到JS引擎所在的執(zhí)行隊(duì)列中

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

  • setInterval與setTimeout所在的線程
  • 定時(shí)任務(wù)并不是由JS引擎計(jì)時(shí)的,是由定時(shí)觸發(fā)線程來(lái)計(jì)時(shí)的
  • 計(jì)時(shí)完畢后,通知事件觸發(fā)線程

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

  • 瀏覽器有一個(gè)單獨(dú)的線程用于處理AJAX請(qǐng)求
  • 當(dāng)請(qǐng)求完成時(shí),若有回調(diào)函數(shù),通知事件觸發(fā)線程

當(dāng)我們了解了渲染進(jìn)程包含的這些線程后,我們思考兩個(gè)問(wèn)題:

  • 為什么 javascript 是單線程的
  • 為什么 GUI 渲染線程為什么與 JS 引擎線程互斥

為什么 javascript 是單線程的

首先是歷史原因,在創(chuàng)建 javascript 這門語(yǔ)言時(shí),多進(jìn)程多線程的架構(gòu)并不流行,硬件支持并不好。

其次是因?yàn)槎嗑€程的復(fù)雜性,多線程操作需要加鎖,編碼的復(fù)雜性會(huì)增高。

而且,如果同時(shí)操作 DOM ,在多線程不加鎖的情況下,最終會(huì)導(dǎo)致 DOM 渲染的結(jié)果不可預(yù)期。

為什么 GUI 渲染線程與 JS 引擎線程互斥

這是由于 JS 是可以操作 DOM 的,如果同時(shí)修改元素屬性并同時(shí)渲染界面(即 JS線程和UI線程同時(shí)運(yùn)行), 那么渲染線程前后獲得的元素就可能不一致了。

因此,為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)定 GUI渲染線程和JS引擎線程為互斥關(guān)系, 當(dāng)JS引擎線程執(zhí)行時(shí)GUI渲染線程會(huì)被掛起,GUI更新則會(huì)被保存在一個(gè)隊(duì)列中等待JS引擎線程空閑時(shí)立即被執(zhí)行。

從 Event Loop 看 JS 的運(yùn)行機(jī)制

到了這里,終于要進(jìn)入我們的主題,什么是 Event Loop

先理解一些概念:

  • JS 分為同步任務(wù)和異步任務(wù)
  • 同步任務(wù)都在JS引擎線程上執(zhí)行,形成一個(gè)執(zhí)行棧
  • 事件觸發(fā)線程管理一個(gè)任務(wù)隊(duì)列,異步任務(wù)觸發(fā)條件達(dá)成,將回調(diào)事件放到任務(wù)隊(duì)列中
  • 執(zhí)行棧中所有同步任務(wù)執(zhí)行完畢,此時(shí)JS引擎線程空閑,系統(tǒng)會(huì)讀取任務(wù)隊(duì)列,將可運(yùn)行的異步任務(wù)回調(diào)事件添加到執(zhí)行棧中,開始執(zhí)行

在前端開發(fā)中我們會(huì)通過(guò)setTimeout/setInterval來(lái)指定定時(shí)任務(wù),會(huì)通過(guò)XHR/fetch發(fā)送網(wǎng)絡(luò)請(qǐng)求, 接下來(lái)簡(jiǎn)述一下setTimeout/setIntervalXHR/fetch到底做了什么事

我們知道,不管是setTimeout/setIntervalXHR/fetch代碼,在這些代碼執(zhí)行時(shí), 本身是同步任務(wù),而其中的回調(diào)函數(shù)才是異步任務(wù)。

當(dāng)代碼執(zhí)行到setTimeout/setInterval時(shí),實(shí)際上是JS引擎線程通知定時(shí)觸發(fā)器線程,間隔一個(gè)時(shí)間后,會(huì)觸發(fā)一個(gè)回調(diào)事件, 而定時(shí)觸發(fā)器線程在接收到這個(gè)消息后,會(huì)在等待的時(shí)間后,將回調(diào)事件放入到由事件觸發(fā)線程所管理的事件隊(duì)列中。

當(dāng)代碼執(zhí)行到XHR/fetch時(shí),實(shí)際上是JS引擎線程通知異步http請(qǐng)求線程,發(fā)送一個(gè)網(wǎng)絡(luò)請(qǐng)求,并制定請(qǐng)求完成后的回調(diào)事件, 而異步http請(qǐng)求線程在接收到這個(gè)消息后,會(huì)在請(qǐng)求成功后,將回調(diào)事件放入到由事件觸發(fā)線程所管理的事件隊(duì)列中。

當(dāng)我們的同步任務(wù)執(zhí)行完,JS引擎線程會(huì)詢問(wèn)事件觸發(fā)線程,在事件隊(duì)列中是否有待執(zhí)行的回調(diào)函數(shù),如果有就會(huì)加入到執(zhí)行棧中交給JS引擎線程執(zhí)行

用一張圖來(lái)解釋:

再用代碼來(lái)解釋一下:

let timerCallback = function() {
  console.log('wait one second');
};
let httpCallback = function() {
  console.log('get server data success');
}
// 同步任務(wù)
console.log('hello');
// 同步任務(wù)
// 通知定時(shí)器線程 1s 后將 timerCallback 交由事件觸發(fā)線程處理
// 1s 后事件觸發(fā)線程將 timerCallback 加入到事件隊(duì)列中
setTimeout(timerCallback,1000);
// 同步任務(wù)
// 通知異步http請(qǐng)求線程發(fā)送網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求成功后將 httpCallback 交由事件觸發(fā)線程處理
// 請(qǐng)求成功后事件觸發(fā)線程將 httpCallback 加入到事件隊(duì)列中
$.get('www.xxxx.com',httpCallback);
// 同步任務(wù)
console.log('world');
//...
// 所有同步任務(wù)執(zhí)行完后
// 詢問(wèn)事件觸發(fā)線程在事件事件隊(duì)列中是否有需要執(zhí)行的回調(diào)函數(shù)
// 如果沒(méi)有,一直詢問(wèn),直到有為止
// 如果有,將回調(diào)事件加入執(zhí)行棧中,開始執(zhí)行回調(diào)代碼

總結(jié)一下:

  • JS引擎線程只執(zhí)行執(zhí)行棧中的事件
  • 執(zhí)行棧中的代碼執(zhí)行完畢,就會(huì)讀取事件隊(duì)列中的事件
  • 事件隊(duì)列中的回調(diào)事件,是由各自線程插入到事件隊(duì)列中的
  • 如此循環(huán)

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

當(dāng)我們基本了解了什么是執(zhí)行棧,什么是事件隊(duì)列之后,我們深入了解一下事件循環(huán)中宏任務(wù)、微任務(wù)

什么是宏任務(wù)

我們可以將每次執(zhí)行棧執(zhí)行的代碼當(dāng)做是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行), 每一個(gè)宏任務(wù)會(huì)從頭到尾執(zhí)行完畢,不會(huì)執(zhí)行其他。

我們前文提到過(guò)JS引擎線程和GUI渲染線程是互斥的關(guān)系,瀏覽器為了能夠使宏任務(wù)和DOM任務(wù)有序的進(jìn)行,會(huì)在一個(gè)宏任務(wù)執(zhí)行結(jié)果后,在下一個(gè)宏任務(wù)執(zhí)行前,GUI渲染線程開始工作,對(duì)頁(yè)面進(jìn)行渲染。

// 宏任務(wù)-->渲染-->宏任務(wù)-->渲染-->渲染...

主代碼塊,setTimeout,setInterval等,都屬于宏任務(wù)

第一個(gè)例子:

document.body.style = 'background:black';
document.body.style = 'background:red';
document.body.style = 'background:blue';
document.body.style = 'background:grey';

我們可以將這段代碼放到瀏覽器的控制臺(tái)執(zhí)行以下,看一下效果:

我們會(huì)看到的結(jié)果是,頁(yè)面背景會(huì)在瞬間變成灰色,以上代碼屬于同一次宏任務(wù),所以全部執(zhí)行完才觸發(fā)頁(yè)面渲染,渲染時(shí)GUI線程會(huì)將所有UI改動(dòng)優(yōu)化合并,所以視覺(jué)效果上,只會(huì)看到頁(yè)面變成灰色。

第二個(gè)例子:

document.body.style = 'background:blue';
setTimeout(function(){
    document.body.style = 'background:black'
},0)

執(zhí)行一下,再看效果:

我會(huì)看到,頁(yè)面先顯示成藍(lán)色背景,然后瞬間變成了黑色背景,這是因?yàn)橐陨洗a屬于兩次宏任務(wù),第一次宏任務(wù)執(zhí)行的代碼是將背景變成藍(lán)色,然后觸發(fā)渲染,將頁(yè)面變成藍(lán)色,再觸發(fā)第二次宏任務(wù)將背景變成黑色。

什么是微任務(wù)

我們已經(jīng)知道宏任務(wù)結(jié)束后,會(huì)執(zhí)行渲染,然后執(zhí)行下一個(gè)宏任務(wù), 而微任務(wù)可以理解成在當(dāng)前宏任務(wù)執(zhí)行后立即執(zhí)行的任務(wù)。

也就是說(shuō),當(dāng)宏任務(wù)執(zhí)行完,會(huì)在渲染前,將執(zhí)行期間所產(chǎn)生的所有微任務(wù)都執(zhí)行完。

Promise,process.nextTick等,屬于微任務(wù)。

第一個(gè)例子:

document.body.style = 'background:blue'
console.log(1);
Promise.resolve().then(()=>{
    console.log(2);
    document.body.style = 'background:black'
});
console.log(3);

執(zhí)行一下,再看效果:

控制臺(tái)輸出 1 3 2 , 是因?yàn)?promise 對(duì)象的 then 方法的回調(diào)函數(shù)是異步執(zhí)行,所以 2 最后輸出

頁(yè)面的背景色直接變成黑色,沒(méi)有經(jīng)過(guò)藍(lán)色的階段,是因?yàn)椋覀冊(cè)诤耆蝿?wù)中將背景設(shè)置為藍(lán)色,但在進(jìn)行渲染前執(zhí)行了微任務(wù), 在微任務(wù)中將背景變成了黑色,然后才執(zhí)行的渲染

第二個(gè)例子:

setTimeout(() => {
    console.log(1)
    Promise.resolve(3).then(data => console.log(data))
}, 0)
setTimeout(() => {
    console.log(2)
}, 0)
// print : 1 3 2

上面代碼共包含兩個(gè) setTimeout ,也就是說(shuō)除主代碼塊外,共有兩個(gè)宏任務(wù), 其中第一個(gè)宏任務(wù)執(zhí)行中,輸出 1 ,并且創(chuàng)建了微任務(wù)隊(duì)列,所以在下一個(gè)宏任務(wù)隊(duì)列執(zhí)行前, 先執(zhí)行微任務(wù),在微任務(wù)執(zhí)行中,輸出 3 ,微任務(wù)執(zhí)行后,執(zhí)行下一次宏任務(wù),執(zhí)行中輸出 2

總結(jié)

  • 執(zhí)行一個(gè)宏任務(wù)(棧中沒(méi)有就從事件隊(duì)列中獲?。?/li>
  • 執(zhí)行過(guò)程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊(duì)列中
  • 宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
  • 當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染
  • 渲染完畢后,JS線程繼續(xù)接管,開始下一個(gè)宏任務(wù)(從事件隊(duì)列中獲?。?/li>

以上就是JS進(jìn)階之從多線程到Event Loop全面梳理的詳細(xì)內(nèi)容,更多關(guān)于JS Event Loop的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JS實(shí)現(xiàn)將圖片轉(zhuǎn)為base64格式

    JS實(shí)現(xiàn)將圖片轉(zhuǎn)為base64格式

    Base64是一種用64個(gè)字符來(lái)表示任意二進(jìn)制數(shù)據(jù)的方法,這篇文章主要為大家介紹了如何實(shí)現(xiàn)將圖片轉(zhuǎn)為base64格式,感興趣的小伙伴可以學(xué)習(xí)一下
    2023-07-07
  • js獲取上傳文件的絕對(duì)路徑實(shí)現(xiàn)方法

    js獲取上傳文件的絕對(duì)路徑實(shí)現(xiàn)方法

    下面小編就為大家?guī)?lái)一篇js獲取上傳文件的絕對(duì)路徑實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-08-08
  • 小程序顯示彈窗時(shí)禁止下層的內(nèi)容滾動(dòng)實(shí)現(xiàn)方法

    小程序顯示彈窗時(shí)禁止下層的內(nèi)容滾動(dòng)實(shí)現(xiàn)方法

    這篇文章主要介紹了小程序顯示彈窗時(shí)禁止下層的內(nèi)容滾動(dòng)實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-03-03
  • echart自適應(yīng)寬度的兩種方法實(shí)例

    echart自適應(yīng)寬度的兩種方法實(shí)例

    最近工作中element后臺(tái)管理使用Echarts圖表,在使用過(guò)程中也遇到了些問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于echart自適應(yīng)寬度的兩種方法,需要的朋友可以參考下
    2022-12-12
  • JS使用onerror捕獲異常示例

    JS使用onerror捕獲異常示例

    這篇文章主要介紹了JS使用onerror捕獲異常的方法,結(jié)合實(shí)例形式分析了javascript基于onerror事件處理器捕獲異常的具體流程與相關(guān)操作技巧,需要的朋友可以參考下
    2016-08-08
  • MQTT.js 入門使用教程

    MQTT.js 入門使用教程

    MQTT.js?是一個(gè)開源的?MQTT 協(xié)議的客戶端庫(kù),使用 JavaScript 編寫,主要用于 Node.js 和 瀏覽器環(huán)境中,這篇文章主要介紹了MQTT.js 入門教程,需要的朋友可以參考下
    2023-04-04
  • javascript與CSS復(fù)習(xí)(二)

    javascript與CSS復(fù)習(xí)(二)

    我們接下來(lái)討論元素的可見(jiàn)性,CSS有兩種不同的方式可以有效的隱藏元素,它們有各自的特點(diǎn),會(huì)導(dǎo)致不同的結(jié)果。
    2010-06-06
  • JavaScript中跨域問(wèn)題的深入理解

    JavaScript中跨域問(wèn)題的深入理解

    這篇文章主要給大家介紹了關(guān)于JavaScript中跨域問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • JS隨拖拽速度設(shè)置傾斜角度的實(shí)現(xiàn)代碼

    JS隨拖拽速度設(shè)置傾斜角度的實(shí)現(xiàn)代碼

    這篇文章主要給大家介紹了JS如何隨拖拽速度設(shè)置傾斜角度,文中有詳細(xì)的代碼講解,對(duì)大家的學(xué)習(xí)或工作有一定的幫助,感興趣的小伙伴可以自己動(dòng)手嘗試一下
    2023-09-09
  • ionic2打包android時(shí)gradle無(wú)法下載的解決方法

    ionic2打包android時(shí)gradle無(wú)法下載的解決方法

    這篇文章主要為大家詳細(xì)介紹了ionic2打包android時(shí)gradle無(wú)法下載的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-04-04

最新評(píng)論

新郑市| 阿克陶县| 通城县| 临西县| 阳曲县| 汉沽区| 宁河县| 襄垣县| 阿克陶县| 宽城| 旅游| 璧山县| 婺源县| 河北省| 陆良县| 松滋市| 磴口县| 沐川县| 扎鲁特旗| 巫溪县| 河北省| 唐河县| 高阳县| 凤凰县| 宽城| 西峡县| 岗巴县| 宁德市| 广元市| 柳林县| 咸丰县| 波密县| 乌海市| 金湖县| 安阳县| 黔西县| 军事| 金山区| 信丰县| 新乡市| 韶山市|