一、什么叫事件循環(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ù)。如下圖:
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