欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一篇文章讓你搞清楚JavaScript事件循環(huán)

 更新時間:2022年06月09日 11:59:15   作者:諸葛小愚  
通過JS的事件循環(huán)機(jī)制,可以更清楚JS代碼的執(zhí)行流,下面這篇文章主要給大家介紹了關(guān)于如何通過一篇文章讓你搞清楚JavaScript事件循環(huán)的相關(guān)資料,需要的朋友可以參考下

前言

異步函數(shù)也是有執(zhí)行順序的。本質(zhì)上來說,JavaScript是單線程語言,不管是在瀏覽器中還是nodejs環(huán)境下。瀏覽器在執(zhí)行js代碼和渲染DOM節(jié)點都是在同一個線程中,執(zhí)行js代碼就無法渲染DOM,渲染DOM的時候就無法執(zhí)行js代碼。如果按照這種同步方式執(zhí)行,頁面的渲染將會出現(xiàn)白屏甚至是報錯,特別是遇到一些耗時比較長的網(wǎng)絡(luò)請求或者js代碼,因此在實際開發(fā)中一般是通過異步的方式解決。

什么是異步?js是一步一步執(zhí)行代碼的,遇到alert這種阻塞代碼時,js將會停止往下執(zhí)行直到阻塞代碼執(zhí)行完畢。異步就是將函數(shù)放在單獨的異步隊列中,不會產(chǎn)生阻塞,js可以繼續(xù)往下執(zhí)行,等到同步代碼執(zhí)行完畢后再執(zhí)行異步隊列中的函數(shù)。因此,js會先執(zhí)行完同步代碼,才會執(zhí)行異步代碼。異步函數(shù)之間,雖然都是異步,但是還是有相對的執(zhí)行順序。

異步函數(shù)的執(zhí)行主要依靠事件循環(huán)來處理,本文重點探討異步的分類(宏任務(wù)、微任務(wù))、事件循環(huán)以及異步函數(shù)的執(zhí)行順序。

宏任務(wù)

宏任務(wù),也可簡單的說成是任務(wù),在下一輪DOM渲染之后執(zhí)行。常見的宏任務(wù)有:

  • setTimeout:設(shè)置一個定時器,該定時器會在設(shè)置的延遲時間到期后執(zhí)行一個函數(shù)或者指定的代碼塊。值得注意的是,setTimeout不一定會在延遲時間到達(dá)后就立即執(zhí)行函數(shù),而是會判斷執(zhí)行隊列中是否還有函數(shù)沒有處理,如果沒有了并且棧為空,setTimeout才會在延遲時間到達(dá)后執(zhí)行函數(shù)。

    // setTimeout 延遲執(zhí)行不等于到期時立即執(zhí)行
    let now = new Date().getSeconds();
    setTimeout(() => {
        console.log('this is setTimeout 0');
    }, 0);
    setTimeout(() => {
        console.log('this is setTimeout 200');
    }, 200);
    while(true) {
        if (new Date().getSeconds() - now >= 2) {
            console.log('break out while loop');
            break;
        }
    }

    運行結(jié)果

  • break out while loop
    this is setTimeout 0
    this is setTimeout 200

    先執(zhí)行同步代碼,再執(zhí)行異步。setTimeout(() => {}, 0)表示0毫秒后立即執(zhí)行函數(shù),但是當(dāng)前執(zhí)行隊列中還有未處理完的while循環(huán),因此需要等到while循環(huán)執(zhí)行完畢后,才會根據(jù)延遲到期時間執(zhí)行函數(shù)。

  • setInterval:設(shè)置定時器,表示在固定的時間間隔內(nèi),重復(fù)執(zhí)行某一函數(shù)或者特定的代碼塊。注意使用setInterval有最小延遲時間限制以及確保執(zhí)行時間要小于間隔時間,如果執(zhí)行時間無法確定,則應(yīng)采用遞歸調(diào)用setTimeout的方式代替。

  • 網(wǎng)絡(luò)請求:只要是指XMLHttpRequest等網(wǎng)絡(luò)請求

微任務(wù)

微任務(wù),在下一輪DOM渲染之前執(zhí)行,微任務(wù)比宏任務(wù)更早執(zhí)。常見的微任務(wù)有:

  • promise:表示一個異步操作最終的結(jié)果和返回值,可能會失敗,也可能成功。異步函數(shù)在執(zhí)行時,什么時候返回結(jié)果是不可預(yù)料的,Promise把異步操作的返回值和函數(shù)關(guān)聯(lián)起來,保證在異步執(zhí)行結(jié)束后會執(zhí)行對應(yīng)的函數(shù),并通過函數(shù)返回操作值。這種效果就類似于把異步代碼“同步執(zhí)行”。
  • queueMicrotask:將函數(shù)添加到微任務(wù)隊
console.log('start');
// 微任務(wù)隊列
Promise.resolve().then(() => {
    console.log('promise then');
});
queueMicrotask(() => {
    console.log('queueMicrotask');
});
console.log('end');

運行結(jié)果

start
end
promise then
queueMicrotask

事件循環(huán)

因為有異步操作的存在,所以出現(xiàn)了事件循環(huán),如果都是同步操作,一行一行執(zhí)行代碼,事件循環(huán)也就失去了用武之地。在了解事件循環(huán)前,還需要補充js的執(zhí)行過程:

js在執(zhí)行代碼時,遇到函數(shù)就會將其添加到調(diào)用棧中,每一幀都會存儲當(dāng)前函數(shù)的參數(shù)和局部變量,當(dāng)一個函數(shù)執(zhí)行完畢,則會從調(diào)用棧中彈出,直到棧被清空,那么程序也就執(zhí)行完畢。在執(zhí)行的過程中,需要的引用數(shù)據(jù)都是從堆中獲取。

在實際開發(fā)中,往往是同步代碼和異步代碼都有。在js執(zhí)行時,還是從第一行代碼開始執(zhí)行,遇到函數(shù)就將其添加到棧中,然后執(zhí)行同步操作;如果遇到異步函數(shù),則根據(jù)其類型,宏任務(wù)就添加到宏任務(wù)隊列,微任務(wù)添加到微任務(wù)隊列。直到同步代碼執(zhí)行完畢,則開始執(zhí)行異步操作。

異步操作后于同步操作,異步操作內(nèi)部也是分先后順序的??偟膩碚f:

  • 微任務(wù)先于宏任務(wù)執(zhí)行
  • 微任務(wù)與微任務(wù)之間根據(jù)先后順序執(zhí)行,宏任務(wù)與宏任務(wù)之間根據(jù)延遲時間順序執(zhí)行
  • 微任務(wù)在下一輪DOM渲染前執(zhí)行,宏任務(wù)在下一輪DOM渲染之后執(zhí)行
  • 每個任務(wù)的執(zhí)行都是一次出棧操作,直到棧被清空

微任務(wù)比宏任務(wù)先執(zhí)行

console.log('start');
// 宏任務(wù)隊列
setTimeout(() => {
    console.log('setTimeout');
});
// 微任務(wù)隊列
Promise.resolve().then(() => {
    console.log('promise then');
});
console.log('end');
// 執(zhí)行結(jié)果
start
end
promise then
setTimeout

微任務(wù)在下一輪DOM渲染前執(zhí)行,宏任務(wù)在之后執(zhí)行

let div = document.createElement('div');
div.innerHTML = 'hello world';
document.body.appendChild(div);
let divList = document.getElementByTagName('div');
console.log('同步任務(wù) length ---', list.length);
console.log('start');
setTimeout(() => {
    console.log('setTimeout length ---', list.length);
    alert('宏任務(wù) setTimeout 阻塞'); // 使用alert阻塞js執(zhí)行
});
Promise.resolve().then(() => {
    console.log('promise then length ---', list.length);
    alert('微任務(wù) promise then 阻塞);
});
console.log('end');

事件循環(huán)

event loop會持續(xù)監(jiān)聽是否有異步操作,如果有則添加到對應(yīng)的隊列中,等待執(zhí)行。例如在宏任務(wù)中添加微任務(wù),或者在微任務(wù)中添加宏任務(wù),當(dāng)前任務(wù)執(zhí)行完后,可能還會有新的任務(wù)添加到事件循環(huán)中。

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

  • 微任務(wù)中創(chuàng)建宏任務(wù)

        new Promise((resolve) => {
          console.log('promise 1');
          setTimeout(() => {
            console.log('setTimeout 1');
          }, 500);
          resolve();
        }).then(() => {
          console.log('promise then');
          setTimeout(() => {
            console.log('setTimeout 2');
          }, 0);
        });
        new Promise((resolve) => {
          console.log('promise 2');
          resolve();
        })

    運行結(jié)果

  • promise 1
    promise 2
    promise then
    setTimeout 2
    setTimeout 1

    解析

    js執(zhí)行代碼,遇到兩個Promise,則分別添加到微任務(wù)隊列,同步代碼執(zhí)行完畢。

    在微任務(wù)隊列中根據(jù)先進(jìn)先出,第一個Promise先執(zhí)行,遇到setTimeout,則添加到宏任務(wù)隊列,resolve()返回執(zhí)行結(jié)果并執(zhí)行then,事件循環(huán)將其繼續(xù)添加到微任務(wù)隊列;第一個Promise執(zhí)行完畢,執(zhí)行第二個Promise。

    繼續(xù)執(zhí)行微任務(wù)隊列,直到清空隊列。遇到setTimeout,并將其添加到宏任務(wù)隊列

    宏任務(wù)隊列現(xiàn)在有兩個任務(wù)待執(zhí)行,由于第二個setTimeout的延遲事件更小,則優(yōu)先執(zhí)行第二個;如果相等,則按照順序執(zhí)行。

    繼續(xù)執(zhí)行宏任務(wù)隊列,直到清空隊列。

  • 宏任務(wù)中創(chuàng)建微任務(wù)

        setTimeout(() => {
          console.log('setTimeout 1');
          new Promise((resolve) => {
            console.log('promise 1');
            resolve();
          }).then(() => {
            console.log('promise then');
          })
        }, 500);
        setTimeout(() => {
          console.log('setTimeout 2');
          new Promise((resolve) => {
            console.log('promise 2');
            resolve();
          })
        }, 0);

    運行結(jié)果

  • setTimeout 2
    promise 2
    setTimeout 1
    promise 1
    promise then

    解析

    js執(zhí)行代碼,遇到兩個setTimeout,將其添加到宏任務(wù)隊列,同步代碼執(zhí)行完畢

    先檢查微任務(wù)隊列中是否有待處理的,剛開始肯定沒有,因此直接執(zhí)行宏任務(wù)隊列中的任務(wù)。第二個為零延遲,需要優(yōu)先執(zhí)行。遇到Promise,將其添加到微任務(wù)隊列。第一個宏任務(wù)執(zhí)行完畢

    在執(zhí)行第二個宏任務(wù)時,微任務(wù)隊列中已經(jīng)存在待處理的,因此需要先執(zhí)行微任務(wù)。

    微任務(wù)執(zhí)行完畢,并且延遲時間到期,第一個setTimeout開始執(zhí)行。遇到Promise,將其添加到微任務(wù)隊列中

    執(zhí)行微任務(wù)隊列中的Promise,執(zhí)行完畢后遇到then,則將其繼續(xù)添加到微任務(wù)隊列

    直到所有微任務(wù)執(zhí)行完畢

  • 宏任務(wù)中創(chuàng)建宏任務(wù)

        setTimeout(() => {
          console.log('setTimeout 1');
          setTimeout(() => {
            console.log('setTimeout 2');
          }, 500);
          setTimeout(() => {
            console.log('setTimeout 3');
          }, 500);
          setTimeout(() => {
            console.log('setTimeout 4');
          }, 100);
        }, 0);

    運行結(jié)果

  • setTimeout 1
    setTimeout 4
    setTimeout 2
    setTimeout 3

    解析

    宏任務(wù)中創(chuàng)建宏任務(wù),執(zhí)行順序一般來說是按照先后順序的。對于setTImeout來說,延遲時間相同,則按照先后順序執(zhí)行;延遲時間不同,則按照延遲時間的大小先后順序執(zhí)行

  • 微任務(wù)中創(chuàng)建微任務(wù)

        new Promise((resolve) => {
          console.log('promise 1');
          new Promise((resolve) => {
            console.log('promise 2');
            resolve();
          });
          new Promise((resolve) => {
            console.log('promise 3');
            resolve();
          })
          resolve();
        })

    運行結(jié)果

  • promise 1
    promise 2
    promise 3

    解析

    微任務(wù)中創(chuàng)建微任務(wù),執(zhí)行順序一般來說是按照先后順序執(zhí)行的。

總結(jié)

  • 同步代碼直接執(zhí)行,異步代碼添加到宏任務(wù)隊列或者微任務(wù)隊列
  • 微任務(wù)在下一輪DOM渲染前執(zhí)行,宏任務(wù)在下一輪DOM渲染之后執(zhí)行
  • 事件循環(huán)持續(xù)監(jiān)聽
  • 如果存在異步操作,需要將關(guān)聯(lián)代碼放在異步函數(shù)中執(zhí)行;或者將異步函數(shù)轉(zhuǎn)為同步操作
  • 如果代碼層次比較復(fù)雜,同步、異步代碼混雜,一定要理清代碼的執(zhí)行順序。避免因為異步,導(dǎo)致代碼出現(xiàn)難以察覺的bug

參考資料

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

相關(guān)文章

最新評論