瀏覽器事件環
程式碼執行順序:預設先執行主棧中的程式碼,主棧執行完成後清空微任務對列,微任務對列清空後,取出第一個巨集任務到主棧中執行,執行完成後再去檢查微任務對列是否有未執行任務,如果有清空,若沒有取下一個巨集任務到主棧中執行,執行完成後再去檢查微任務對列是否有未執行任務,如果有清空...形成一個事件環
棧(Stack)
是一種遵循後進先出(last in first out LIFO)原則的有序集合;在棧裡,新元素都靠近棧頂,舊元素都靠近棧底(現實中棧的例子:一摞書或者堆放在一起的盤子)
- 棧頂:新新增的或待刪除的元素儲存的一端
佇列(Queue)
是一種遵循先進先出(first in first out FIFO)原則的一組有序的項;在佇列中,最新新增的元素必須排在佇列的末尾(現實中佇列的例子:排隊過安檢)
巨集任務
setTimeout
setImmediate
(只有IE
支援)- 比
setTimeout
快
- 比
messageChannel
- 在
web worker
中可用 - 允許建立一個新的訊息通道,並通過它的兩個
MessagePort
屬性傳送資料
- 在
白話說明:我們小的時候都玩過土電話,我們可以新的訊息通道想象成我們的土電話,把兩個
MessagePort
屬性想像成圖中的小熊(port2
)和小兔(port1
),當小兔通過土電話向小熊喊話(port1
向另個port2
傳送資料),聲音的傳遞過程(資料傳遞的過程就是)就是非同步的。
<html>
<body>
<div id="app"></div>
<script>
console.log('start')
// 這裡我們為了測試他是非同步的,我們採用port1先傳送資料,port2後監聽
let channel = new MessageChannel();
let port1 = channel.port1;
let port2 = channel.port2;
port2.postMessage('你好')
setTimeout(()=>{
console.log('setTimeout');
},0)
port1.onmessage = function (e) {
console.log(e);
}
console.log('end')
</script>
</body>
</html>
複製程式碼
微任務
Promise.resolve.then
MutationObserver
DOM3 Events
規範的一部分- 提供了監視對
DOM
樹所做更改的能力
<html>
<body>
<div id = "app"></div>
<script>
/* 實現 等待dom更新後 再取dom中的資料 */
setTimeout(()=>{
console.log('timeout');
},0)
// 1.建立一個MutationObserver的例項並傳入一個非同步的callback
let mutationObserver = new MutationObserver(()=>{
console.log(app.children.length); // 非同步執行
})
// 2.觀察app的childList的變化
mutationObserver.observe(app,{childList:true})
// 3.通過指令碼動態向app中插入30個p
for (let i = 0; i < 10; i++) {
app.appendChild(document.createElement('p'));
}
for (let i = 0; i < 10; i++) {
app.appendChild(document.createElement('p'));
}
for (let i = 0; i < 10; i++) {
app.appendChild(document.createElement('p'));
}
</script>
</body>
</html>
複製程式碼
小題開胃
console.log('start')
setTimeout(()=>{
console.log(1);
Promise.resolve().then(()=>{
console.log(2)
})
},0);
Promise.resolve().then(data=>{
console.log(3);
setTimeout(()=>{
console.log(4);
})
})
console.log('end');
// start end 3 1 2 4
複製程式碼
- 圖文解析
難題鞏固
async function async1() {
console.log('async1Start');
await async2();
console.log('async1End');
}
async function async2() {
console.log('async2');
}
console.log('scriptStart');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('scriptEnd');
//scriptStart async1Start async2 promise1 scriptEnd async1End promise2 setTimeout
複製程式碼
- 圖文解析
思考題
- 分析以下程式碼如何輸出?為什麼?
let guYan = async () => {
console.log(await new Promise((resolve, reject) => {
console.log('guYan');
}))
}
guYan()
複製程式碼
注:前面的兩道題的分析中沒加入巨集、微任務事件池的概念(就是在進入佇列之前需要先經過事件池的排列)
node事件環
- 每個階段都有一個自己的佇列
- 在低版本
node
中在主執行棧執行完畢和切換佇列的時候清空微任務 - 在高版本(11+)
node
中和瀏覽器一樣在主執行棧執行完畢和一個巨集任務執行完畢後清空微任務
// 我們排除掉node內部自己呼叫的迴圈部分,其實需要我們掌握的部分並不多,我總結為三部分
// 1.timer階段主要代表setTimeout
// 2.poll 輪循階段 主要是i/o的讀寫(fs模組的一些操作等)
// 3.check階段主要代表setImmediate
┌───────────────────────────┐
┌─>│ 1.timers │──┐
│ └─────────────┬─────────────┘ │ ┌───────────────┐ |
│ ┌─────────────┴─────────────┐ │ │ incoming: │ | 執
│ │ 2.poll │<─────┤ connections, │ | 行
│ └─────────────┬─────────────┘ │ │ data, etc. │ | 順
│ ┌─────────────┴─────────────┐ │ └───────────────┘ | 序
└──│ 3.check │<─┘ ↓
└───────────────────────────┘
/*
*執行順序分析:
* 1.從timer階段開始檢查是否有可執行的若沒有向下執行
* 2.在poll階段執行完畢後檢查timer階段是否待執行的,若有準備跳回timer階段執行;
* 但是在這之前需要檢查check階段是否有待執行的,若有先跳到check階段執行,再跳到timer階段執行,若沒有可直接跳回timer階段執行;
* 3.無條件按照順序執行,沒有可跳過
*/
複製程式碼
巨集任務
setTimeout
setImmediate
setInterval
readFile
檔案讀寫系列
微任務
promise.then
process.nextTick
案例解析
案例一
setTimeout(()=>{
console.log('setTimeout');
},0)
setImmediate(()=>{
console.log('setImmediate');
})
複製程式碼
- 上面的程式碼你頻繁的在
node
環境中執行你會發現兩種結果- 1.先輸出
setTimeout
再輸出setImmediate
,這種情況我不做說明哈就是正常情況 - 2.先輸出
setImmediate
再輸出setTimeout
,這種情況的原因是:當我們程式執行的時候我們知道setTimeout
的第二個引數代表時間雖然我們寫成0
但是最小時間並不是0
,大約是4
。所以當執行到timer
階段的時候setTimeout
的執行時間還沒到,直接進行到poll
階段等待,等待一段時候後發現timer
階段的setTimeout
可以執行,準備回到timer
階段執行,但是回去之前需要檢查check
階段,發現有setImmediate
需要執行
- 1.先輸出
案例二
let fs = require('fs');
fs.readFile(__filename,'utf-8',(err,data)=>{
setTimeout(()=>{
console.log('setTimeout')
},0)
setImmediate(()=>{
console.log('setImmediate')
})
)
複製程式碼
- 程式執行發現
timer
階段沒有可執行的,直接進入poll
階段,在poll
階段分別給timer
階段和check
階段增加一個待執行函式後開始等待setTimeout
的執行,當setTimeout
可執行後,準備從poll
階段跳到timer
之前檢查check
階段有可執行的setImmediate
所以永遠是先輸出setImmediate
後輸出setTimeout
小概念總結
程式和執行緒的關係
- 程式:計算機分配任務和排程任務的最小單位(程式是執行緒的集合)
js
中一個程式只有一個主執行緒- 中
js
執行緒和UI
執行緒是互斥的
- 中
如何提高js
程式碼的載入速度
defer
- 表示指令碼會被延遲到文件完全被解析和顯示之後再執行,載入後續文件元素的過程將和
js
指令碼的載入並行進行(即實現了非同步載入js
指令碼),這樣頁面載入會變快
<script src="index.js" defer></script> 複製程式碼
- 表示指令碼會被延遲到文件完全被解析和顯示之後再執行,載入後續文件元素的過程將和
async
(h5屬性)- 指令碼一旦可用,執行就會非同步
- 不保證指令碼的載入順序按照引入的順序,因此一旦使用就要保證所需載入的
js
指令碼無依賴關係
<script src="index.js" async></script> 複製程式碼
preload
(會先載入資源)prefetch
(預設不會馬上載入,等待瀏覽器空閒時偷偷載入)