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之前呢??