在說 Js 事件循環(huán)前,我們需要先搞清楚一些相關(guān)概念,比如:進程,線程(多線程,單線程),主線程,執(zhí)行棧,同步任務,異步任務(宏任務、微任務),任務隊列(宏隊列、微隊列)。
創(chuàng)新互聯(lián)專注于龍子湖企業(yè)網(wǎng)站建設,自適應網(wǎng)站建設,商城系統(tǒng)網(wǎng)站開發(fā)。龍子湖網(wǎng)站建設公司,為龍子湖等地區(qū)提供建站服務。全流程按需定制制作,專業(yè)設計,全程項目跟蹤,創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務進程:每個應用程序至少會有一個進程。例如瀏覽器應用程序,當瀏覽器啟動后,它會在瀏覽器內(nèi)部啟動多個進程,如GPU進程,網(wǎng)絡進程,渲染進程等。每個進程里可以有一個或多個線程。進程之間也是彼此獨立,各自占據(jù)一個自己的內(nèi)存空間。
線程:負責處理進程分配的任務,它沒有自己的內(nèi)存空間,但可以共享使用進程的內(nèi)存空間。如果把電腦操作系統(tǒng)比喻成一個寫字樓,一個應用程序就如同寫字樓里的一家公司,一個進程就好比公司里的一個部門,而一個線程就是部門里的一個員工,所有的線程(員工)共享一個進程(部門)的內(nèi)存(資源)。
多線程:就是多個線程同時在處理一個任務,類似公司部門里多個員工分工合作共同去完成同一個項目。
單線程:就是一個任務,只有一個線程在處理,意味著任務多時,就需要排隊等候。
而 JavaScript從誕生開始,大的特點就是單線程,即,同一個時間只能做一件事。JavaScript 的單線程,與它的用途有關(guān)。作為瀏覽器腳本語言,JavaScript 的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定 JavaScript 同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容,另一個線程刪除了這個節(jié)點,這時瀏覽器應該以哪個線程為準?
所以,為了避免復雜性,從一誕生,JavaScript 就是單線程,這已經(jīng)成了這門語言的核心特征,將來也不會改變。
為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript 腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質(zhì)。
作為單線程語言,JavaScript 的執(zhí)行順序為從上到下,且同一時刻只能執(zhí)行一個任務,這就意味著排隊。當遇到一個耗時比較長的任務時,主線程不得不停下來,等到前一個任務執(zhí)行完之后,再開始執(zhí)行下一個任務,這樣很容易造成阻塞。如果排隊是因為計算量大,CPU忙不過來也就算了。但由于類似ajax網(wǎng)絡請求、setTimeout時間延遲、DOM事件的用戶交互等,這些任務并不消耗 CPU,而是一種空等,白白浪費很多時間,很不合理。
為了保證JavaScript 代碼能夠高效無阻塞地運行,因此出現(xiàn)了事件循環(huán)(Event Loop)。
事件循環(huán),包含了 主線程,執(zhí)行棧,同步任務,異步任務(宏任務,微任務),任務隊列(宏隊列,微隊列)等。
首先,JavaScript 在執(zhí)行的過程中,會創(chuàng)建一個JavaScript主線程和一個執(zhí)行棧,并把所有任務分為同步任務和異步任務。同步任務會依次被放入執(zhí)行棧中,執(zhí)行棧里的任務(同步任務)以先進先出的方式,被主線程調(diào)度執(zhí)行。異步任務,則會被交給相應的異步模塊去處理,一旦異步處理完成后有了結(jié)果,就往任務隊列的末尾添加注冊一個回調(diào)事件(回調(diào)函數(shù)),等待主線程空閑時調(diào)用。這樣,主線程就只管從執(zhí)行棧中調(diào)度執(zhí)行同步任務,直到清空執(zhí)行棧,這時就會去查詢?nèi)蝿贞犃小H绻蝿贞犃欣镉腥蝿?,就將排在最前面的任務調(diào)入主線程中執(zhí)行。如果執(zhí)行時,又有異步任務,就又把異步任務交給異步模塊處理,返回結(jié)果后,再次往任務隊列里注冊回調(diào)事件。以此類推,形成一個事件環(huán)。這樣主線程就無需等待,可以不停地執(zhí)行任務,大大提升了主線程的效率。
主線程:執(zhí)行JavaScript同步任務的場所。它同一時刻,照順序每次只能從執(zhí)行棧中調(diào)入一個同步任務并立即執(zhí)行。
執(zhí)行棧:存放所有的同步任務,里面的任務以先進先出的方式被主線程調(diào)度執(zhí)行。
同步任務:JavaScript運行后,按照順序,自動進入執(zhí)行棧,并依次被主線程調(diào)度且立即執(zhí)行的任務。
console.log(1);
console.log(2);
// console.log() 是同步任務,按照順序,依次進入執(zhí)行棧,被主線程調(diào)度執(zhí)行后,依次輸出 1,2
異步任務:執(zhí)行后,存在無法立即處理的任務,主要有setTimeout, setInterval,XHR, Fetch,addEventListener,Promise等。
setTimeout(() =>{
console.log(1)
}, 1000);
// setTimeout是異步任務,執(zhí)行后,需要等待1000毫秒后,才會輸出 1
異步任務又分宏任務和微任務。
宏任務:setTimeout, setInterval,XHR, Fetch,addEventListener, 宏任務屬于一輪事件循環(huán)的開頭,在事件循環(huán)過程中遇到的宏任務,會被放到下一輪循環(huán)去執(zhí)行。script的整體代碼在執(zhí)行時,就是一個宏任務。
微任務:Promise,是一輪事件循環(huán)的最后一個環(huán)節(jié),所有的微任務執(zhí)行完,一輪事件循環(huán)就結(jié)束了。注*(new Promise() 函數(shù)本身是同步任務,只有它的then/catch/finally等方法是微任務)。
setTimeout(() =>console.log(1), 0);
Promise.resolve().then(() =>console.log(2));
console.log(3);
const p = new Promise((resolve, reject) =>{
? ? setTimeout(() =>{
? ? ? ? resolve();
? ? }, 1000);
? ? console.log(4);
});
p.then(() =>console.log(5));
// 上述整體代碼為一個宏任務開始運行,運行后,JavaScript主線程開始從上到下執(zhí)行代碼
// 第1行代碼屬于異步任務中的宏任務,其任務類型是延遲任務,放入宏隊列中的延遲隊列,等候下一輪循環(huán)執(zhí)行
// 第2行代碼是一個異步任務中的微任務,放入任務隊列中的微隊列,等候執(zhí)行棧空閑后被主線程調(diào)用執(zhí)行
// 第3行代碼是同步任務,進入執(zhí)行棧后,被主線程直接調(diào)度且立即執(zhí)行,輸出 3
// 第4行代碼,new Promise() 本身屬同步任務
// 所以開始執(zhí)行內(nèi)部的 setTimeout,發(fā)現(xiàn)是個宏任務,放入宏隊列,等候下一輪循環(huán)執(zhí)行
// 接著執(zhí)行同步任務 console.log(4), 直接輸出 4
// p.then() 由于 new Promise() 里面的 resolve() 未執(zhí)行到,目前仍 padding 未完成狀態(tài),所以不會被執(zhí)行
// 此時,執(zhí)行棧已經(jīng)清空,主線程開始查詢?nèi)蝿贞犃?,發(fā)現(xiàn)有一個微任務,調(diào)入主線程中執(zhí)行,于是輸出 2
// 至此,本輪事件循環(huán)結(jié)束,開始第2輪循環(huán),由于第1個 setTimeout 最早在任務隊列里注冊回調(diào)事件,所以先執(zhí)行
// 于是,在第2輪循環(huán)中,console.log(1) 作為一個同步任務,被主線程直接調(diào)用執(zhí)行,輸出 1
// 接著開始第3輪循環(huán),執(zhí)行第2個 setTimeout 里的 resolve(),此時 p 的狀態(tài)變成了已完成,開始調(diào)用then()方法
// p.then() 是一個微任務,于是被放入微隊列,但當前循環(huán)的執(zhí)行棧里沒有任務,該微任務直接被調(diào)入主線程執(zhí)行,輸出 5
// 最終,上述代碼的執(zhí)行結(jié)果為:3-4-2-1-5
任務隊列:存放著所有異步任返回結(jié)果后所注冊的回調(diào)事件,等待執(zhí)行??臻e時主線程的調(diào)用。過去把任務隊列分為宏隊列和微隊列,這種說法目前已經(jīng)無法滿足復雜的瀏覽器環(huán)境,取而代之的是一種靈活多變的處理方式。
根據(jù) W3C 官方的解釋,不同的任務可以有不同的類型,分屬不同隊列,不同的隊列有不同的優(yōu)先級,而同類型的任務必須放在同一個隊列。在一個事件循環(huán)中,必須有一個微隊列,相對于其他隊列,微隊列具有最高的優(yōu)先級,必須優(yōu)先調(diào)度執(zhí)行。其他隊列則由瀏覽器自行決定取哪一個隊列的任務執(zhí)行。
setTimeout, setInterval 的任務需要等待計時完成后再執(zhí)行,屬于延遲隊列;
XHR, Fetch 的任務需要等待網(wǎng)絡通信完成后再執(zhí)行,屬于通信隊列;
addEventListener 的任務需要等待用戶操作后再執(zhí)行,屬于交互隊列;
Promise的then/catch/finally 的任務為微任務,屬于微隊列。
一般的隊列優(yōu)先情況是: 微隊列 >交互隊列 >延遲隊列。
來看個例子,下面的代碼會輸出什么?
console.log(1);
setTimeout(() =>{
fn(2);
console.log(3);
setTimeout(() =>console.log(4), 1000);
}, 0);
Promise.resolve().then(() =>{
console.log(5);
});
fn(6);
function fn(x) {
console.log(x);
}
// 第一步,執(zhí)行 console.log(1),這是一個同步任務,直接輸出 1;
// 第二步,執(zhí)行 setTimeout,這是一個這是一個宏任務,留到下一次循環(huán)再執(zhí)行,主線程繼續(xù)往下執(zhí)行;
// 第三步,執(zhí)行 Promise,這也是一個異步任務,交給異步模塊處理,繼續(xù)往后執(zhí)行;
// 第四步,執(zhí)行 fn(6),這是一個同步任務,直接執(zhí)行,輸出6;
// 此時,執(zhí)行棧已經(jīng)清空沒任務了,主線程處于空閑狀態(tài),于是就去查詢?nèi)蝿贞犃校?// 發(fā)現(xiàn)任務隊列的微隊列里有一個微任務 Promise.resolve().then(), 于是調(diào)入主線程執(zhí)行,輸出5;
// 至此,第一輪事件循環(huán)結(jié)束。
// 第五步,開始第二輪事件循環(huán),此時,任務隊列的延遲隊列里有一個 setTimeout 任務,執(zhí)行它;
// 于是,第六步,執(zhí)行 fn(2),函數(shù)里面的 console.log(x)是一個同步任務,直接輸出 2;
// 第六步,執(zhí)行 console.log(3),同步任務,直接輸出 3;
// 第七步,又是一個 setTimeout 宏任務,下一循環(huán)執(zhí)行,第二輪事件循環(huán)結(jié)束。
// 開始第三輪循環(huán),此時,只有一個 console.log(4) 的同步任務,直接輸出 4;
// 最終,上述代碼的執(zhí)行結(jié)果為:1-6-5-2-3-4
再看一個例子。
const p = new Promise((resolve, reject) =>{
? ? setTimeout(() =>{
? ? ? ? console.log(1);
? ? }, 1000);
? ? console.log(2);
? ? setTimeout(() =>{
? ? ? ? Promise.resolve().then(() =>{
? ? ? ? console.log(3);
? ? ? ? });
console.log(4);
? ? ? ? resolve(5);
}, 0);
});
p.then(v =>console.log(v)).catch(r =>console.log(r));
console.log(6);
setTimeout(() =>{
console.log(7);
}, 0);
// 第一步,執(zhí)行 new Promise(),注意,new Promise() 本身是一個同步任務于是開始執(zhí)行 Promise 函數(shù)內(nèi)部的第一個 setTimeout 任務,這個一個宏任務,1000毫秒后會在任務隊里注冊一個的回調(diào)事件a
// 第二步,執(zhí)行 console.log(2) ,這是一個同步任務,直接輸出 2
// 第三步,執(zhí)行第二個 setTimeout 宏任務,該任務會立即在任務隊列里注冊一個回調(diào)事件b
// 第四步,由于 Promise 目前還仍然處于padding 狀態(tài),所以,p.then() 不會執(zhí)行
// 第五步,執(zhí)行 console.log(6),直接輸出 6
// 第六步,執(zhí)行一個0秒的 setTimeout 宏任務,立即在任務隊列里注冊了一個回調(diào)事件 c
// 至此,執(zhí)行棧已經(jīng)清空,微隊列里沒東西,任務隊列里有3個宏任務,其順序為 b, c, a,第一個事件循環(huán)結(jié)束
// 于是,第二個事件循環(huán)開始,主線程開始執(zhí)行宏任務b
// 第七步,執(zhí)行任務 b 的 Promise,在微隊列里注冊了一個微任務
// 第八步,執(zhí)行 console.log(4),這是一個同步任務,直接輸出 4
// 第九步,執(zhí)行 resolve(5),這是一個同步任務,此時直接將 Promise 的狀態(tài)變成fulfilled(已成功)
// 此時 p.then(...) 會自動被調(diào)用,在微隊列里注冊了第二個微任務
// 第十步,此時第一個宏任務 b 的執(zhí)行棧已清空,主線程開始查詢?nèi)蝿贞犃?,發(fā)現(xiàn)微隊列里有兩個微任務,開始執(zhí)行微任務
// 第十一步,執(zhí)行第一個微任務,輸出 3
// 第十二步,執(zhí)行第二個微任務,輸出 5
// 第十三步,執(zhí)行第二個宏任務 c,輸出 7
// 第十四步,執(zhí)行第三個宏任務 a,輸出 1
// 最終,上述代碼的執(zhí)行結(jié)果為:2-6-4-3-5-7-1
個人理解,如有說的不對的地方,歡迎留言指正。
參考資料 https://www.ruanyifeng.com/blog/2014/10/event-loop.html
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
文章標題:Javascrip事件循環(huán),就看這個,超詳細解讀-創(chuàng)新互聯(lián)
鏈接URL:http://jinyejixie.com/article42/jegec.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站、手機網(wǎng)站建設、App開發(fā)、做網(wǎng)站、網(wǎng)站建設、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容