trigger click 和 user click 有什么區別嗎?
好像沒有的。直到發現了這樣一段代碼。
<button class="btn1">Button</button>
var btn1 = document.querySelector('.btn1');
btn1.addEventListener('click', function () {
Promise.resolve().then(function() {
console.log('Microtask 1');
})
console.log('Listener 1');
});
btn1.addEventListener('click', function () {
Promise.resolve().then(function() {
console.log('Microtask 2');
})
console.log('Listener 2');
});
點擊click按鈕會出什么結果呢?
Listener 1
Microtask 1
Listener 2
Microtask 2
那執行btn1.click()呢?
Listener 1
Listener 2
Microtask 1
Microtask 2
很神奇。
先來一個簡單的問題
以下代碼輸出什么呢?
console.log('start')
setTimeout(()=>{
console.log('setTimeout')
}, 0)
Promise.resolve().then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('end')
為什么會是這個順序呢?先來個圖講解一下。
js的事件循環
看標題就知道原因就是事件循環了。
js是單線程驅動,執行的包含主線程(stack)和任務隊列兩部分,主線程負責處理同步任務,異步任務和回調會加到任務隊列里面,每當主線程完成前一個任務,回調函數就會在一個無限循環圈里被調用,因此這個圈被稱為事件循環。回調函數正在等待輪到自己執行所排的隊就被稱為任務隊列。
還是沒說到回調和異步的執行順序?
macrotask 和 microtask
macrotask 和 microtask就是兩個任務隊列。舉個例子:
macrotasks的操作 setTimeout setInterval setImmediate I/O UI渲染
microtasks的操作 Promise process.nextTick Object.observe MutationObserver
可以簡單的理解為異步操作會加到macrotask隊列,回調等監聽操作會加到microtasks里。
那執行順序是什么?
首先js會執行主線程(stack)的代碼,就是同步的代碼。這時候遇到異步操作怎么辦?加到macrotask隊列里准備執行回調加到 microtasks里准備執行。
主線程(stack)為空的時候,開始執行任務隊列的代碼。這是就開始了一個事件循環。
簡單來說就3步:
- 在 macrotask 隊列中執行最早的那個 task ,然后移出
- 執行 microtask 隊列中所有可用的任務,然后移出
- 下一個循環,執行下一個 macrotask 中的任務 (再跳到第2步)
microtask queue 中的 task總是在當前的循環執行。
一份偽代碼:
for (macroTask of macroTaskQueue) {
// 1. Handle current MACRO-TASK
handleMacroTask();
// 2. Handle all NEXT-TICK
for (nextTick of nextTickQueue) {
handleNextTick(nextTick);
}
// 3. Handle all MICRO-TASK
for (microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}
每次執行macroTask的時候都會清空一下 microTask。
回到簡單的問題。
這是我們再去看一下這個解釋的gif。
console是同步任務,總是入stack,然后同步執行完。執行的過程settimeout會加到macrotask里,回調加到microtask里。
console完成了,stack為空,開始執行microtask,執行microtask的時候可以繼續增加microtask,就像2個then。都執行完之后再執行macrotask的settimeout。
注意,run script 也是一個macrotask,所以執行stack的時候就是在執行第一個macrotask。所以當stack為空會先執行一次microtask。
回到code trigger和user trigger
dalao們應該已經知道為什么有區別了,其實就是macrotask和microtask的問題。
code trigger的時候,stack盞不是空的,所以兩個click的microtask會順序壓入盞中,沒有中斷js的執行,當執行到btn1.click()的時候,stack空,順序執行microtask。
推薦一篇相關文章,有圖形化的講解,特別清楚。click me。
requestanimationframe
requestanimationframe是一個比較特殊的api,對於它,瀏覽器只保證requestAnimationFrame的回調在重繪之前執行,沒有確定的時間,何時重繪由瀏覽器決定。
所以有時候一樣的代碼執行結果不一樣。
requestanimationframe屬於macrotask。
拓展
async的異步調用
async的異步調用,其實還是promis的調用。看一下例子:
async function async1() {
console. log('async1 start' );
await async2();
console.log('async1 end' );
}
async function async2() {
console.log('async2');
}
console.log('script start' );
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
這個輸出什么呢?
為什么async2在promise2前面呢?
看一下這個理解一下->
(來自阮一峰es6 - async的實現原理)
async function fn(args) {
// ...
}
// 等同於
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF(); // 對應 console. log('async1 start' ),console.log('async2');
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
那么了解之后再看看這個?
async function async1() {
console. log('async start' );
await console.log('async2');
await console.log('async3');
await console.log('async4');
console.log('async end' );
}
console.log('script start' );
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
思考一下為什么async4,5之后的await都在promise2之前呢??