亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

JS專題之事件循環(huán)

mengera88 / 2319人閱讀

摘要:宏任務(wù)需要多次事件循環(huán)才能執(zhí)行完??偨Y(jié)事件循環(huán)是和事件調(diào)用機(jī)制的核心,保證了頁面可以有序無阻塞的進(jìn)行。事件循環(huán)的主要邏輯是先執(zhí)行調(diào)用棧,直到清空調(diào)用棧只剩下全局上下文。微任務(wù)執(zhí)行后完,進(jìn)行頁面渲染和垃圾回收后進(jìn)行下一輪事件循環(huán)。

準(zhǔn)備知識(shí)
1. 進(jìn)程(process)

進(jìn)程是系統(tǒng)資源分配一個(gè)獨(dú)立單位,一個(gè)程序至少有一個(gè)進(jìn)程。比方說:一個(gè)工廠代表一個(gè) CPU, 一個(gè)車間就是一個(gè)進(jìn)程,任一時(shí)刻,只能有一個(gè)進(jìn)程在運(yùn)行,其他進(jìn)程處于非運(yùn)行狀態(tài)。

2. 線程(Thread)

線程是CPU調(diào)度和分派的基本單位,一個(gè)線程只能屬于一個(gè)進(jìn)程,一個(gè)進(jìn)程可以有多個(gè)線程且至少有一個(gè)。比方說一個(gè)車間的工人,可以有多個(gè)工人一起工作。

生活中常常能看到,某某電腦 CPU 的 4 核 4 線程,其意思是指,這款 CPU 同一時(shí)間最多只能運(yùn)行 4 個(gè)線程,所以有些線程會(huì)處于工作狀態(tài),有的線程會(huì)處于中斷,堵塞,睡眠狀態(tài)。

經(jīng)??吹接泻芏嗳蝿?wù)同時(shí)在進(jìn)行,一邊工作,一邊聽歌,還一邊下載電影。那是因?yàn)檫@些線程在以閃電般的速度不斷的切換主要的幾個(gè)線程,所以,人的體驗(yàn)上感覺是很多很多任務(wù)在同時(shí)進(jìn)行。

3. 棧(stack)

棧是一種數(shù)據(jù)結(jié)構(gòu),具有后進(jìn)先出的特點(diǎn),最開始進(jìn)入棧結(jié)構(gòu)的數(shù)據(jù)反而最后才能出來。

4. 隊(duì)列(queue)

隊(duì)列也是一種數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)只能從一邊進(jìn),一邊出,先進(jìn)去的自然就先出來。

5. 同步和異步(sync async)

同步和異步關(guān)注的消息通信機(jī)制,同步在函數(shù)調(diào)用時(shí),如果調(diào)用者沒有拿到響應(yīng)結(jié)果,程序會(huì)繼續(xù)等待,知道拿到結(jié)果為止。而異步會(huì)執(zhí)行其后的代碼,等到有響應(yīng)結(jié)果后,才處理響應(yīng)。

6. 阻塞和非阻塞(blocking & non-blocking)

阻塞和非阻塞關(guān)注的是程序等待調(diào)用結(jié)果時(shí)的狀態(tài),阻塞的意思是,在調(diào)用結(jié)果返回響應(yīng)前,線程會(huì)被掛起占用,程序無法繼續(xù)往下走,而非阻塞的線程則不會(huì)掛起,后面的代碼能夠繼續(xù)往下執(zhí)行。

比方說:我去超市買包薯片,老板告訴我貨架上沒貨了,馬上去庫房拿,這過程中,老板要我站著等他,直到他拿到貨出來給我。這個(gè)過程就是阻塞。

如果老板告訴我,可以先回去,他一會(huì)去庫房拿,拿到了之后打電話給我。這個(gè)過程,就是非阻塞的,我不用等待,還可以干其他的事情。

7. 執(zhí)行棧(execution stack)

js 代碼在執(zhí)行代碼時(shí),JS 會(huì)給調(diào)用代碼生成一個(gè)執(zhí)行上下文對(duì)象,并將其壓入執(zhí)行上下文棧,首先進(jìn)入棧底的是全局上下文,然后是函數(shù)的執(zhí)行上下文(Execution Context),函數(shù)執(zhí)行完之后,函數(shù)上下文從棧中彈出,直到退出瀏覽器,全局上下文才從棧底彈出。

用代碼舉個(gè)例子:

var globalName = "window";

var foo1 = function() {
    console.log("foo1");
}

var foo2 = function() {
    console.log("foo2");
    foo1();
}

foo2();

上面的圖片大致能夠描述執(zhí)行上下文棧的實(shí)現(xiàn)邏輯,有關(guān)執(zhí)行上下文的知識(shí),大家可以翻看我之前的文章 - 《JavaScript 之執(zhí)行上下文》

二、為什么 JS 是單線程模型?
JavaScript 的一個(gè)非常有趣的特性是事件循環(huán)模型,與許多其他語言不同,它永不阻塞。 處理 I/O 通常通過事件和回調(diào)來執(zhí)行  -- MDN

瀏覽器主要任務(wù)是給用戶是視覺和交互上的體驗(yàn),如果頁面使用過程中,偶爾出現(xiàn)阻塞、掛起、無響應(yīng)的體驗(yàn)一定是非常糟糕的。同時(shí),如果采用多線程同步的模型,那么如何保證同一時(shí)間修改了 DOM, 到底是哪個(gè)線程先生效呢。

瀏覽器執(zhí)行環(huán)境的核心思想在于任務(wù)調(diào)度方式的特別:

哪個(gè)任務(wù)的優(yōu)先級(jí)高,先來就先運(yùn)行,直到執(zhí)行完了才執(zhí)行下一個(gè),并且同一時(shí)刻只能執(zhí)行一個(gè)代碼片段,即所謂的單線程模型。

比方說,銀行的柜臺(tái)只開啟了一個(gè)柜臺(tái),每個(gè)人想要辦理業(yè)務(wù),就得先拿號(hào)排隊(duì),叫到了你的號(hào)碼,你才能上去辦理業(yè)務(wù)。不能多個(gè)人同時(shí)在一個(gè)柜臺(tái)辦理業(yè)務(wù),不然就很容易出差錯(cuò)。

三、事件循環(huán)

事件循環(huán)是 JS 處理各種事件的核心,由于多個(gè)線程同時(shí)操作 DOM, 造成不可控的問題,所以 JS 采用了單線程模型。另外,由于所有的事件同步執(zhí)行,執(zhí)行完一個(gè)才能執(zhí)行下一個(gè),會(huì)造成頁面渲染的堵塞。JS 中存在異步事件,用戶可以在點(diǎn)擊頁面的時(shí)候,請(qǐng)求網(wǎng)絡(luò)響應(yīng)的同事,還可以進(jìn)行其他的點(diǎn)擊操作,保證了頁面不會(huì)因?yàn)榫W(wǎng)絡(luò)請(qǐng)求,多種 IO 接口響應(yīng)慢造成代碼執(zhí)行的堵塞和掛起。

事件循環(huán)的順序是:

進(jìn)入 script 標(biāo)簽,創(chuàng)建全局上下文

執(zhí)行全局上下文中的函數(shù),將其壓入執(zhí)行調(diào)用棧

某個(gè)函數(shù)執(zhí)行完后,函數(shù)彈出執(zhí)行棧,清空函數(shù)上下文中的變量對(duì)象和內(nèi)存空間,判斷是否需要更新渲染,如果需要?jiǎng)t更新渲染。

如果遇到異步事件,也會(huì)壓入執(zhí)行調(diào)用棧,但瀏覽器識(shí)別到它是異步事件后,會(huì)將其彈出執(zhí)行棧,然后將異步事件的回調(diào)函數(shù)放入事件隊(duì)列中。

執(zhí)行直到函數(shù)調(diào)用棧清空只剩全局執(zhí)行上下文,這時(shí),JS 會(huì)檢查事件隊(duì)列中是否有事件,如果有,則將事件隊(duì)列中的一個(gè)事件出隊(duì),然后壓入執(zhí)行棧中執(zhí)行。

當(dāng)執(zhí)行棧又清空只剩全局執(zhí)行上下文時(shí),又會(huì)重復(fù)第 5 步。這就是 JS 的事件循環(huán)。

當(dāng)用戶關(guān)閉瀏覽器,全局執(zhí)行上下文彈出執(zhí)行棧,清空相應(yīng)上下文中的變量對(duì)象和內(nèi)存空間。

接下來我們用代碼來解釋:

console.log("script start!");

function foo1() {
    console.log("foo1");
}

foo1();

setTimeout(function () {
    console.log("setTimeout!");
}, 1000);

function foo2() {
    console.log("foo2");
}

foo2();

console.log("script end!");

打?。?// script start!
// foo1
// foo2
// script end!

// setTimeout!

那我們嘗試把 setTimeout 的延遲時(shí)間改為 0,想要立即執(zhí)行,看會(huì)不會(huì)立即執(zhí)行:

console.log("script start!");

function foo1() {
    console.log("foo1");
}

foo1();

setTimeout(function () {
    console.log("setTimeout!");
}, 0);

function foo2() {
    console.log("foo2");
}

foo2();

console.log("script end!");

打?。?// script start!
// foo1
// foo2
// script end!
// setTimeout!

可以看出 setTimeout 屬于異步事件,總是會(huì)在主線程的任務(wù)執(zhí)行完后才開始執(zhí)行。

順便說一下事件循環(huán)幾個(gè)原則:

一次只處理一個(gè)任務(wù)

一個(gè)任務(wù)從開始到完成,不會(huì)被其他任務(wù)所中斷

這兩個(gè)原則保證了瀏覽器任務(wù)單元的完整性,事件調(diào)用的有序性。

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

事件循環(huán)的實(shí)現(xiàn)本來應(yīng)該由一個(gè)用于宏任務(wù)的隊(duì)列和一個(gè)用于微任務(wù)的隊(duì)列進(jìn)行完成,這使得事件循環(huán)要根據(jù)任務(wù)類型來進(jìn)行優(yōu)先處理。

宏任務(wù):
宏任務(wù)包括:

創(chuàng)建文檔對(duì)象、解析 HTML、執(zhí)行主線程代碼(script)

執(zhí)行各種事件:頁面加載、輸入、點(diǎn)擊

setTimout,setInterval 異步事件

宏任務(wù)代表一個(gè)個(gè)離散、獨(dú)立的工作單元,運(yùn)行完任務(wù)后,瀏覽器可以進(jìn)行其他的任務(wù)調(diào)度,如更新渲染或執(zhí)行垃圾回收。宏任務(wù)需要多次事件循環(huán)才能執(zhí)行完。

微任務(wù):
微任務(wù)包括:

Promise 回調(diào)函數(shù)

new MutaionObserver()

微任務(wù)是更小的任務(wù),微任務(wù)需要盡可能地、通過異步方式執(zhí)行,微任務(wù)更新瀏覽器的狀態(tài),但必須在瀏覽器執(zhí)行其他任務(wù)之前執(zhí)行。微任務(wù)使得我們避免不必要的 UI 重繪。微任務(wù)在一次事件循環(huán)中必須全部執(zhí)行完。

宏任務(wù)和微任務(wù)的執(zhí)行優(yōu)先級(jí)原則是:

完成一個(gè)宏任務(wù)后,執(zhí)行余下的微任務(wù)

同一次事件循環(huán)中,宏任務(wù)永遠(yuǎn)在微任務(wù)之前執(zhí)行。

ok,知道了優(yōu)先級(jí)原則后,我們來看一段代碼:

console.log(1);

setTimeout(function() {
    console.log(2);
    new Promise(resolve => {
        console.log(3);
        resolve(4);
        console.log(5);
    }).then(data => {
        console.log(data);
    });
}, 0);

new Promise(resolve => {
    console.log(6);
    resolve(7);
    console.log(8);
}).then(data => {
    console.log(data);
});

setTimeout(function() {
    console.log(9);
}, 0);

console.log(10);

output:  
第一次循環(huán):
// 1
// 6
// 8
// 10
// 7

第二次循環(huán):
// 2
// 3
// 5
// 4


第三次循環(huán)
// 9

我們一起來分析以上代碼:

進(jìn)入第一次事件循環(huán),script 這個(gè)宏任務(wù),輸出 1

第一個(gè) setTimeout 函數(shù)本身是函數(shù)調(diào)用,屬于任務(wù)源,setTimeout 的回調(diào)函數(shù),即第一個(gè)參數(shù),才是被分發(fā)的任務(wù),任務(wù)被加入宏任務(wù)隊(duì)列,第二次循環(huán)時(shí)調(diào)用。

Promise 屬于微任務(wù),但是 Promise 初始化中代碼會(huì)立即進(jìn)行。所以會(huì)立即輸出 6 和 8;

Promise 初始化后的回調(diào)放入微任務(wù)隊(duì)列

第二個(gè) setTimeout 也屬于宏任務(wù)源,回調(diào)函數(shù)的任務(wù)放入宏任務(wù)隊(duì)列,第三次事件循環(huán)時(shí)調(diào)用

繼續(xù)調(diào)用棧,輸出 10, 沒毛病

第一次事件循環(huán)的宏任務(wù)執(zhí)行完畢,執(zhí)行余下的所有微任務(wù),所以輸出 7,

第二次事件循環(huán),發(fā)現(xiàn)有宏任務(wù),即第一個(gè) setTimeout 的回調(diào),輸出 2,調(diào)用 Promise 構(gòu)建函數(shù)的調(diào)用棧,直接執(zhí)行,所以輸出3 和 5

第一個(gè) setTimeout 的 promise 回調(diào)放入微任務(wù)隊(duì)列。

第二次事件循環(huán)的宏任務(wù)調(diào)用執(zhí)行完,執(zhí)行剛才前一步 Promise 創(chuàng)建的微任務(wù),輸出 4,第二次循環(huán)執(zhí)行完畢。

進(jìn)入第 3 次事件循環(huán),只有一個(gè)宏任務(wù),即第二個(gè) SetTimeout,所以輸出 9;

關(guān)于事件循環(huán)宏任務(wù)和微任務(wù)的執(zhí)行過程:

首先兩個(gè)類型的任務(wù)都是逐個(gè)執(zhí)行

微任務(wù)會(huì)前下一個(gè)渲染或垃圾回收前全部執(zhí)行完

一次事件循環(huán)中先只執(zhí)行一個(gè)宏任務(wù),在下一次事件循環(huán)前執(zhí)行完所有的微任務(wù),包括新創(chuàng)建的微任務(wù)。

五、web worker

盡管 HTML5 新標(biāo)準(zhǔn)加入了 web worker 的多線程技術(shù),但是 web worker 只能用于計(jì)算,并且 JS 的多線程 worker 無法操作 DOM, 不然就無法控制頁面是在被誰操作的了。

主線程傳給子線程的數(shù)據(jù)是通過拷貝復(fù)制,同樣子線程傳給主線程的數(shù)據(jù)也是通過拷貝復(fù)制,而不是共享同一個(gè)內(nèi)存空間。

以上說明,JS 不存在線程同步,所以還是可以把 JS 看做單線程模型,把 web worker 當(dāng)做 JS 的一種回調(diào)機(jī)制。

總結(jié)

事件循環(huán)是 JS 和 Nodejs 事件調(diào)用機(jī)制的核心,保證了頁面可以有序無阻塞的進(jìn)行。

事件循環(huán)的主要邏輯是先執(zhí)行調(diào)用棧,直到清空調(diào)用棧只剩下全局上下文。

然后 JS 檢查宏任務(wù)隊(duì)列,如果有任務(wù)則取出一個(gè)進(jìn)行調(diào)用,進(jìn)行頁面渲染和垃圾回收。

同時(shí)將所有的微任務(wù)源派發(fā)的任務(wù)加入微任務(wù)事件隊(duì)列,最后執(zhí)行余下的所有微任務(wù)。微任務(wù)執(zhí)行后完,進(jìn)行頁面渲染和垃圾回收后進(jìn)行下一輪事件循環(huán)。

歡迎關(guān)注我的個(gè)人公眾號(hào)“謝南波”,專注分享原創(chuàng)文章。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/99366.html

相關(guān)文章

  • JS專題數(shù)組去重

    摘要:將元素作為對(duì)象的鍵,默認(rèn)鍵對(duì)應(yīng)的值為如果對(duì)象中沒有這個(gè)鍵,則將這個(gè)元素放入結(jié)果數(shù)組中去。 前言 數(shù)組去重在日常開發(fā)中的使用頻率還是較高的,也是網(wǎng)上隨便一抓一大把的話題,所以,我寫這篇文章目的在于歸納和總結(jié),既然很多人都在提的數(shù)組去重,自己到底了解多少呢。又或者是如果自己在開發(fā)中遇到了去重的需求,自己能想到更好的解決方案嗎。 這次我們來理一理怎么做數(shù)組去重才能做得最合適,既要考慮兼容性,...

    only_do 評(píng)論0 收藏0
  • JS異步編程callback

    摘要:而異步則是相反,調(diào)用在發(fā)出之后,這個(gè)調(diào)用就直接返回了,所以沒有返回結(jié)果而是在調(diào)用發(fā)出后,被調(diào)用者通過狀態(tài)通知來通知調(diào)用者,或通過回調(diào)函數(shù)處理這個(gè)調(diào)用??偨Y(jié)回調(diào)函數(shù)是異步編程中的基石,但同時(shí)也存在很多問題,不太適合人類自然語言的線性思維習(xí)慣。 為什么 JS 是單線程? 眾所周知,Javascript 語言的執(zhí)行環(huán)境是單線程(single thread)。 所謂單線程,就是指一次只能完成一...

    superw 評(píng)論0 收藏0
  • JavaScript專題系列文章

    摘要:專題系列共計(jì)篇,主要研究日常開發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫一個(gè)函數(shù),輸入,返回。 JavaScript 專題之從零實(shí)現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 ext...

    Maxiye 評(píng)論0 收藏0
  • JS專題垃圾回收

    摘要:如果沒有引用指向該對(duì)象零引用,對(duì)象將被垃圾回收機(jī)制回收。經(jīng)過增量標(biāo)記改進(jìn)后,垃圾回收的最大停頓時(shí)間可以減少到原來的左右。解除引用的真正作用是讓值脫離執(zhí)行環(huán)境,以便垃圾收集器下次運(yùn)行時(shí)將其回收。 前言 在講 JS 的垃圾回收(Garbage Collection)之前,我們回顧上一篇《JS專題之memoization》,memoization 的原理是以參數(shù)作為 key,函數(shù)結(jié)果作為 v...

    liujs 評(píng)論0 收藏0
  • [面試專題]JS異步原理(事件,隊(duì)列)

    摘要:全部代碼是一個(gè)先執(zhí)行一個(gè)執(zhí)行過程中遇到等異步操作則創(chuàng)建一個(gè)遇到等創(chuàng)建一個(gè)這兩個(gè)分別被掛起執(zhí)行棧為空時(shí)開始處理完成后處理直到該全部執(zhí)行完然后繼續(xù)主線程調(diào)用棧注每一次事件循環(huán),只處理一個(gè)。 JS異步原理(事件,隊(duì)列) 調(diào)用棧 JS執(zhí)行時(shí)會(huì)形成調(diào)用棧,調(diào)用一個(gè)函數(shù)時(shí),返回地址、參數(shù)、本地變量都會(huì)被推入棧中,如果當(dāng)前正在運(yùn)行的函數(shù)中調(diào)用另外一個(gè)函數(shù),則該函數(shù)相關(guān)內(nèi)容也會(huì)被推入棧頂.該函數(shù)執(zhí)...

    LeoHsiun 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<