事件循環(huán)(Event loop)

一、什么叫事件循環(huán)
事件循環(huán)也就是Event loop, 是JavaScript或Node為解決單線程代碼執(zhí)行不阻塞主進(jìn)程一種機(jī)制,也就是我們所說的異步原理。事件循環(huán)負(fù)責(zé)執(zhí)行代碼运怖、收集和處理事件以及執(zhí)行隊列中的子任務(wù)。

二夏伊、什么是進(jìn)程與線程摇展?
進(jìn)程是計算機(jī)中正在運(yùn)行的程序的一個實例;每個進(jìn)程都是有獨立的內(nèi)存空間溺忧,彼此之間互不影響咏连;是進(jìn)行資源調(diào)度的一個基本單位。類似于安裝在手機(jī)上的一個應(yīng)用鲁森。
線程是進(jìn)程中執(zhí)行的一個實體單位祟滴;復(fù)雜應(yīng)用中一個進(jìn)程通常包括多個線程,用于同時執(zhí)行不同的任務(wù)歌溉,例如游戲應(yīng)用中垄懂,有的線程負(fù)責(zé)網(wǎng)絡(luò)通訊,有的線程負(fù)責(zé)游戲交互和渲染等。類似應(yīng)用中的很多任務(wù)線草慧。

三桶蛔、什么是線程阻塞?
JavaScript的一大特點就是單線程冠蒋,也就是說羽圃,同一個時間只能做一件事。任何在主線程上執(zhí)行的長時間運(yùn)行的任務(wù)(例如復(fù)雜的計算抖剿、大量的循環(huán)等)都會導(dǎo)致執(zhí)行棧無法處理其他事件,包括用戶輸入识窿、UI 更新和異步回調(diào)斩郎。這種情況下,瀏覽器可能會出現(xiàn)卡頓或無響應(yīng)的情況喻频。

四缩宜、JS如何解決線程阻塞?
使用異步編程是解決線程阻塞的主要方法之一甥温。通過將耗時的任務(wù)放入異步回調(diào)中锻煌,可以讓主線程繼續(xù)處理其他任務(wù)。如下圖:

JavaScript任務(wù)分類

1姻蚓、同步任務(wù): 在主線程上排隊執(zhí)行的任務(wù)只有前一個任務(wù)執(zhí)行完畢宋梧,才能執(zhí)行后一個任務(wù),形成一個執(zhí)行棧狰挡。(promise是同步任務(wù))
2捂龄、異步任務(wù): 不進(jìn)入主線程,而是進(jìn)入任務(wù)隊列加叁,當(dāng)主線程中的任務(wù)執(zhí)行完畢倦沧,就從任務(wù)隊列中取出任務(wù)放進(jìn)主線程中來進(jìn)行執(zhí)行。在異步模式下它匕,創(chuàng)建異步任務(wù)主要分為宏任務(wù)與微任務(wù)兩種展融。
(1) 宏任務(wù):是由宿主(瀏覽器、Node)發(fā)起的豫柬,而微任務(wù)由 JS 自身發(fā)起告希。
宏任務(wù)(Macrotask)包括:

1. 整體代碼script;
2. 定時器任務(wù): 如setTimeout轮傍、setInterval暂雹、setImmediate (NodeJS獨有);
3. I/O操作:  網(wǎng)絡(luò)請求创夜,文件讀寫杭跪;
4. 渲染任務(wù):dom渲染,當(dāng)瀏覽器需要重繪或重新布局時觸發(fā)的任務(wù);
5. 異步ajax等涧尿;
6. 用戶交互任務(wù):例如點擊事件系奉、輸入事件等與用戶交互的相關(guān)任務(wù);
7. 請求動畫幀任務(wù):通過requestAnimationFrame()方法設(shè)置的任務(wù)姑廉,用于在每一幀進(jìn)行繪畫或動畫操作缺亮;

這些任務(wù)都是比較耗時的操作,在事件循環(huán)中被視為宏任務(wù)桥言,需要等待一定時間或特定的觸發(fā)條件才會執(zhí)行萌踱。
宏任務(wù)的執(zhí)行順序:setImmediate --> setTimeout --> setInterval --> i/o操作 --> 異步ajax。

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

1. Promise回調(diào):Promise對象的resolve或reject方法的回調(diào)函數(shù)号阿;Promise的then并鸵、catch、finally回調(diào)扔涧;
2. MutationObserver回調(diào):當(dāng)DOM發(fā)生變化時觸發(fā)的回調(diào)函數(shù)园担;
3. async/await函數(shù)中的后續(xù)操作:在async函數(shù)中使用await等待的操作完成后,緊接著的代碼塊中的任務(wù)枯夜;
4. process.nextTick:進(jìn)程對象process中的一個方法弯汰。nextTick會在上一次事件循環(huán)結(jié)束,然后在下一次事件循環(huán)開始之前執(zhí)行湖雹。比setTimeout(fn,0)效率高多了咏闪。

微任務(wù)的執(zhí)行順序:process.nextTick --> Promise

五、JavaScript 運(yùn)行機(jī)制
1劝枣、整體的script(作為第一個宏任務(wù))開始執(zhí)行的時候汤踏,會把所有代碼分為兩部分:“同步任務(wù)”、“異步任務(wù)”舔腾;
2溪胶、同步任務(wù)會直接進(jìn)入主線程依次執(zhí)行;
3稳诚、異步任務(wù)會再分為宏任務(wù)(進(jìn)入宏任務(wù)隊列) 和 微任務(wù)(進(jìn)入微任務(wù)隊列)哗脖。
4、當(dāng)主線程內(nèi)的任務(wù)執(zhí)行完畢(主線程為空時)扳还,會檢查微任務(wù)的任務(wù)隊列才避,如果有任務(wù),就進(jìn)入主線程全部執(zhí)行氨距,如果沒有就從宏任務(wù)隊列讀取下一個宏任務(wù)執(zhí)行桑逝;
5、每執(zhí)行完一個宏任務(wù)就清空一次微任務(wù)隊列俏让,此過程會不斷重復(fù)楞遏,這就是Event Loop茬暇。

六、vue中的nextTick
由于vue的更新機(jī)制是異步的寡喝,所以當(dāng)數(shù)據(jù)修改之后糙俗,dom還停留在更新之前,此時想要獲取更新后的dom预鬓,可以使用nextTick巧骚,表示的是下次dom更新循環(huán)結(jié)束后執(zhí)行的回調(diào)。
應(yīng)用場景:created 中獲取dom可以使用nextTick

created() {
    // 使用nextTick可以在created生命周期獲取dom節(jié)點
    this.$nextTick(() => {
        console.log(this.$refs.container);
    })
}

七格二、題目練習(xí)

練習(xí)一

setTimeout(function () {//宏任務(wù)放到隊列中
 console.log('1');
})
new Promise(function (resolve) {
 console.log('2'); //實例化過程是同步任務(wù)劈彪,直接執(zhí)行
 resolve();
}).then(function () { //放到微任務(wù)隊列中
 console.log('3');
})
console.log('4'); //同步任務(wù),直接執(zhí)行
//打印順序 2 4 3 1

分析:

1. 遇到setTimeout顶猜,異步宏任務(wù)將其放到宏任務(wù)列表中粉臊,命名為time1;
2. new Promise 在實例化過程中所執(zhí)行的代碼都是同步執(zhí)行的( function 中的代碼)驶兜,輸出2 ;
3.  將 Promise 中注冊的回調(diào)函數(shù)放到微任務(wù)隊列中远寸,命名為 then1 抄淑;
4.  執(zhí)行同步任務(wù) console.log('4') ,輸出 4 驰后,至此執(zhí)行棧中的代碼執(zhí)?完畢肆资;
5. 從微任務(wù)隊列取出任務(wù) then1 到主線程中,輸出 3 灶芝,至此微任務(wù)隊列為空郑原;
6. 從宏任務(wù)隊列中取出任務(wù) time1 到主線程中,輸出 1 夜涕,至此宏任務(wù)隊列為空犯犁;

練習(xí)二

console.log(1); //同步任務(wù)
setTimeout(function () { //宏任務(wù)
  console.log(2); //宏任務(wù)中的同步任務(wù)
  let promise = new Promise(function (resolve, reject) {
    console.log(3); //宏任務(wù)中的同步任務(wù)
    resolve();
  }).then(function () {
    console.log(4); //宏任務(wù)中的微任務(wù)
  });
}, 1000);
setTimeout(function () { //宏任務(wù)
  console.log(5); //宏任務(wù)中的同步任務(wù)
  let promise = new Promise(function (resolve, reject) {
    console.log(6); //宏任務(wù)中的同步任務(wù)
    resolve();
  }).then(function () {
    console.log(7); //宏任務(wù)中的微任務(wù)
  });
}, 0);
let promise = new Promise(function (resolve, reject) {
  console.log(8); //同步任務(wù)
  resolve()
}).then(function () {
  console.log(9); //微任務(wù)
}).then(function () {
  console.log(10); //微任務(wù)
});
console.log(11); //同步任務(wù)
//執(zhí)行順序:1 8 11 9 10 5 6 7 2 3 4

分析:

1. 執(zhí)?同步任務(wù) console.log(1) ,輸出 `1` 女器;
2. 遇到 setTimeout 放到宏任務(wù)隊列中酸役,命名 time1 ;
3. 遇到 setTimeout 放到宏任務(wù)隊列中驾胆,命名 time2 涣澡;
4. new Promise 在實例化過程中所執(zhí)?的代碼都是同步執(zhí)?的( function 中的代碼),輸出`8` 丧诺;
5. 將 Promise 中注冊的回調(diào)函數(shù)放到微任務(wù)隊列中入桂,命名為 then1 ;
6. 將 Promise 中注冊的回調(diào)函數(shù)放到微任務(wù)隊列中驳阎,命名為 then2 抗愁;
7. 執(zhí)?同步任務(wù) console.log(11)馁蒂, 輸出 `11` ;
8. 從微任務(wù)隊列取出任務(wù) then1 到主線程中驹愚,輸出` 9` 远搪;
9. 從微任務(wù)隊列取出任務(wù) then2 到主線程中,輸出 `10` 逢捺,?此微任務(wù)隊列為空谁鳍;
10. 從宏任務(wù)隊列中取出 time2( 注意這?不是 time1 的原因是 time2 的執(zhí)?時間為 0);
11. 執(zhí)?同步任務(wù) console.log(5) 劫瞳,輸出 `5` 倘潜;
12. new Promise 在實例化過程中所執(zhí)?的代碼都是同步執(zhí)?的( function 中的代碼),輸出`6` 志于;
13. 將 Promise 中注冊的回調(diào)函數(shù)放到微任務(wù)隊列中涮因,命名為 then3 ,?此宏任務(wù)time2執(zhí)?完成伺绽;
14. 從微任務(wù)隊列取出任務(wù) then3 到主線程中养泡,輸出 `7` ,?此微任務(wù)隊列為空奈应;
15. 從宏任務(wù)隊列中取出 time1 澜掩,?此宏任務(wù)隊列為空;
16. 執(zhí)?同步任務(wù) console.log(2) 杖挣,輸出` 2` 肩榕;
17. new Promise 在實例化過程中所執(zhí)?的代碼都是同步執(zhí)?的( function 中的代碼),輸出`3` 惩妇;
18. 將 Promise 中注冊的回調(diào)函數(shù)放到微任務(wù)隊列中株汉,命名為 then4 ,?此宏任務(wù)time1執(zhí)?完成歌殃;
19. 從微任務(wù)隊列取出任務(wù) then4 到主線程中乔妈,輸出` 4 `,?此微任務(wù)隊列為空挺份。

練習(xí)三

//宏任務(wù)執(zhí)行順序: setImmediate --> setTimeout --> setInterval --> i/o操作 --> 異步ajax
let axios = require('axios');
let fs = require('fs');
console.log('begin'); //同步任務(wù)
fs.readFile('1.txt',(err,data)=>{ //宏任務(wù)-讀寫
    console.log('fs');
});
axios.get('https://api.muxiaoguo.cn/api/xiaohua?api_key=fd3270a0a9833e20').then(res=>{ 
    console.log('axios'); //宏任務(wù)-異步的Ajax
});
setTimeout(()=>{ //宏任務(wù)-setTimeout
    console.log('setTimeout')
},0);
setImmediate(()=>{ //宏任務(wù)-setImmediate
  console.log('setImmediate');
});
(async function (){
    console.log('async') //微任務(wù)褒翰?
})();
console.log('end'); //同步任務(wù)
//執(zhí)行順序:begin async end setTimeout setImmediate fs axios

分析:

setImmediate沒有時間參數(shù),它與延遲 0 毫秒的 setTimeout() 回調(diào)?常相似匀泊。所以當(dāng)setTimeout延遲時間也是0毫秒時优训,誰在前面就先執(zhí)行誰。此外如果setTimeout延遲時間不是0毫秒各聘,它的執(zhí)行順序會在 i/o 操作之后揣非。

練習(xí)四

//微任務(wù)之間的執(zhí)行順序:process.nextTick --> Promise
console.log('begin');
Promise.resolve().then(()=>{
  console.log('promise');
})
process.nextTick(()=>{
    console.log('nextTick');
});
console.log('end');
//執(zhí)行順序:begin end nextTick promise

練習(xí)五

// 宏任務(wù)隊列 1
setTimeout(() => {
  // 宏任務(wù)隊列 2.1
  console.log('timer_1');
  setTimeout(() => {
    // 宏任務(wù)隊列 3
    console.log('timer_3')
  }, 0)
  new Promise(resolve => {
    resolve()
    console.log('new promise')
  }).then(() => {
    // 微任務(wù)隊列 1
    console.log('promise then')
  })
}, 0)
 
setTimeout(() => {
  // 宏任務(wù)隊列 2.2
  console.log('timer_2')
}, 0)
console.log('===== Sync queue =====')
//執(zhí)行順序:===== Sync queue =====;timer_1躲因; new promise早敬;promise then忌傻;timer_2;timer_

練習(xí)六

async function async1() { //async--聲明一個函數(shù)是異步的
   console.log('async1 start');
   await async2(); //await--等待一個異步函數(shù)執(zhí)行完成
   console.log('async1 end'); //異步微任務(wù)---await等待的操作完成后搞监,緊接著的代碼塊中的任務(wù)
}
async function async2() {
   console.log('async2');
}

console.log('script start'); //同步任務(wù)

setTimeout(function() {
   console.log('setTimeout'); //異步宏任務(wù)
}, 0)

new Promise(function(resolve) {
   console.log('promise1'); //同步任務(wù)
   resolve();
}).then(function() {
   console.log('promise2'); //異步微任務(wù)
});

async1();

console.log('script end'); //同步任務(wù)
//執(zhí)行順序:script start水孩;promise1;async1 start琐驴;async2俘种;script end;promise2绝淡;async1 end宙刘;setTimeout;

分析:

1. 聲明一個異步函數(shù)async1牢酵;
2. 聲明一個異步函數(shù)async2;
3. 執(zhí)行同步任務(wù)console.log('script start')悬包,輸出`script start`;
4. 遇到 setTimeout 放到宏任務(wù)隊列中馍乙,命名 time1 布近;
5. new Promise 在實例化過程中所執(zhí)?的代碼都是同步執(zhí)?的( function 中的代碼),輸出`promise1` 丝格;
6. 將 Promise 中注冊的回調(diào)函數(shù)放到微任務(wù)隊列中吊输,命名為 w1 ;
7. 執(zhí)行async1函數(shù)铁追,執(zhí)行內(nèi)部同步任務(wù),輸出`async1 start`;
8. 執(zhí)行async2函數(shù)茫船,執(zhí)行async2內(nèi)部同步任務(wù)琅束,輸出`async2`;
9. await任務(wù)后的console.log('async1 end')任務(wù)屬于微任務(wù),加入微任務(wù)隊列算谈,命名w2;
10. 執(zhí)行同步任務(wù)涩禀,輸出`script end`;
11. 同步任務(wù)執(zhí)行完畢,從微任務(wù)隊列取出任務(wù) w1到主線程中,  輸出`promise2`;
12. 從微任務(wù)隊列取出任務(wù) w2到主線程中,  輸出`async1 end`;
13. 從宏任務(wù)隊列中取出 time1到主線程中然眼,輸出`setTimeout`;

練習(xí)七

const promise = new Promise((resolve, reject) => {
  console.log(1); //同步任務(wù)
    
  setTimeout(() => { //異步宏任務(wù)
    console.log('timerStart');  //異步宏任務(wù)中的同步任務(wù)
    resolve('success'); //異步微任務(wù)
    console.log('timerEnd'); //異步宏任務(wù)中的同步任務(wù)
  }, 0)

  console.log(2); //同步任務(wù)
});

promise.then((res) => {
  console.log(res); //異步微任務(wù)
});

console.log(4); //同步任務(wù)
//執(zhí)行順序:1艾船,2,4高每,timerStart屿岂,timerEnd,success
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鲸匿,一起剝皮案震驚了整個濱河市爷怀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌带欢,老刑警劉巖运授,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烤惊,死亡現(xiàn)場離奇詭異,居然都是意外死亡吁朦,警方通過查閱死者的電腦和手機(jī)柒室,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逗宜,“玉大人雄右,你說我怎么就攤上這事〗跸” “怎么了不脯?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刻诊。 經(jīng)常有香客問我防楷,道長,這世上最難降的妖魔是什么则涯? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任复局,我火速辦了婚禮,結(jié)果婚禮上粟判,老公的妹妹穿的比我還像新娘亿昏。我一直安慰自己,他們只是感情好档礁,可當(dāng)我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布角钩。 她就那樣靜靜地躺著,像睡著了一般呻澜。 火紅的嫁衣襯著肌膚如雪递礼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天羹幸,我揣著相機(jī)與錄音脊髓,去河邊找鬼。 笑死栅受,一個胖子當(dāng)著我的面吹牛将硝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屏镊,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼依疼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了而芥?” 一聲冷哼從身側(cè)響起涛贯,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔚出,沒想到半個月后弟翘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虫腋,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年稀余,在試婚紗的時候發(fā)現(xiàn)自己被綠了悦冀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡睛琳,死狀恐怖盒蟆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情师骗,我是刑警寧澤历等,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站辟癌,受9級特大地震影響寒屯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜黍少,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一寡夹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧厂置,春花似錦菩掏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至访忿,卻和暖如春传于,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背醉顽。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留平挑,地道東北人游添。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像通熄,于是被迫代替她去往敵國和親唆涝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容