高級前端進階(五)
- 2022 年 5 月 9 日
- 筆記
- javascript, 事件循環
詳解JavaScript中的事件循環機制!!!
一、簡單講解
這個大家應該或多或少都知道的
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i); // 輸出10個10
});
}
解析:先執行for循環,循環疊加i,然後再執行setTimeout 10遍,所以會輸出10個10
變體:
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i); // 依次輸出0-9
});
}
我們知道var跟let本質區別就是作用域的問題,所以會聯想到let導致的,但在這一點上,並不是作用域問題,因為從事件循環機制來解釋是有問題的
以上的程式碼等價於
for (var i = 0; i < 10; i++) {
(function (i) {
setTimeout(() => {
console.log(i);
});
})(i); // 立即執行函數,以上的let應該是將程式碼特殊處理了下
}
立即執行函數:函數聲明後立即執行。
從事件循環的角度來說,應該先執行for循環將i疊加到10,執行完後再去執行for循環體(setTimeout),此時的i變成了10,所以每次輸出都是10,
這裡使用let,應該是特殊處理了一下的。
二、深入講解
我們知道JavaScript語言是單執行緒的,至於為啥是單執行緒?
假設有兩個執行緒,一個在頁面上新增一個div,另一個執行緒在頁面上刪除div,那最終聽誰的?
那JavaScript怎麼實現非同步的呢?
在JavaScript中,有兩類任務:同步任務和非同步任務。
同步任務:普通的任務,依次從上往下執行。
非同步任務:又分為宏任務、微任務。
宏任務:setTimeout跟setInterval
微任務:Promise().then() 這裡要注意一下,Promise方法裡面的是同步任務,then裡面的才是微任務
執行順序:先執行同步任務,遇到非同步任務,將其放入到宏任務或者微任務隊列中,然後優先執行微任務,接下來再去執行宏任務。
簡單的例子:
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2); // 這裡是同步任務
resolve();
}).then((res) => {
console.log(3);
})
console.log(4);
// 輸出結果是 2 4 3 1
解析:從上往下執行,setTimeout是宏任務,放到宏任務隊列中,
Promise裡面的是同步任務,所以先執行,輸出2,then裡面的是微任務,放到微任務隊列中,
最後一個是同步任務,執行,輸出4,
然後執行微任務,輸出3
最後執行宏任務,輸出1
來點難度的:
console.log(1);
setTimeout(function () {
console.log(2);
}, 0);
setTimeout(function () {
console.log(3);
setTimeout(function () {
console.log(4);
}, 0);
}, 0);
new Promise(function (resolve, reject) {
console.log(5);
resolve();
}).then((res) => {
console.log(6);
new Promise(function (resolve, reject) {
console.log(7);
resolve();
}).then((res) => {
console.log(8);
});
});
new Promise(function (resolve, reject) {
console.log(9);
resolve();
})
.then((res) => {
console.log(10);
})
.then((res) => {
console.log(11);
});
console.log(12);
// 輸出 1 5 9 12
// 6 7 10 8 11
// 2 3 4
解析:原理跟上面一樣,不過需要注意的是,8跟11的順序:,在這裡then的層級,將各個Promise裡面第一層的then放在一起,第二層的then放在一起,依此類推,
然後依次執行第一層的,執行完第一層,接下來執行完第二層,依此類推。
第一層(6,7,10)
第二層(8,11)
所以先輸出8, 再輸出11的。
再來一個(也最可能出錯或者無法理解的):
console.log(1);
async function fn() {
console.log(2);
await console.log(3);
console.log(4); // 這一步,你應該會有問題
}
setTimeout(() => {
console.log(5);
}, 0);
fn();
new Promise((resolve) => {
console.log(6);
resolve();
}).then(() => {
console.log(7);
});
console.log(8);
// 輸出:1 2 3 6 8 4 7 5
解析:async await是Promise的語法糖,只是針對寫法上的優化
將async await翻譯成Promise:
async function fn() {
console.log(2);
await console.log(3);
console.log(4); // 這一步,你應該會有問題
}
// 等價於
function fn() {
return new Promise((resolve, reject) => {
console.log(2);
resolve(
(() => {
console.log(3);
})() // 立即執行函數
);
}).then(() => {
console.log(4);
});
}
這樣,你應該能夠明白,為啥輸出3之後不是立即輸出4了吧!
再來一道加深鞏固的:
console.log(1);
async function fn() {
console.log(2);
await console.log(3);
await console.log(4);
console.log(5);
}
setTimeout(() => {
console.log(6);
}, 2000);
setTimeout(() => {
console.log(7);
}, 1000);
fn();
new Promise((resolve) => {
console.log(8);
new Promise((resolve) => {
console.log(9);
resolve();
}).then(() => {
console.log(10);
});
resolve();
}).then(() => {
console.log(11);
new Promise((resolve) => {
console.log(12);
resolve();
}).then(() => {
console.log(13);
});
});
console.log(14);
// 輸出 1 2 3 8 9 14
// 4 10 11 12 5 13
// 7 6
我相信,你現在應該理解掌握了!
只要鑽的深,鐵杵都能給你磨成針!!!