xaoyege 2019-06-26
// setTimeout中的回调函数才是进入任务队列的任务
setTimeout(function() {
console.log('xxxx');
})
// 非常多的同学对于setTimeout的理解存在偏差。所以大概说一下误解:
// setTimeout作为一个任务分发器,这个函数会立即执行,而它所要分发的任务,也就是它的第一个参数,才是延迟执行事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后,本轮循环结束。下一轮循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
当我们在执行setTimeout任务中遇到setTimeout时,它仍然会将对应的任务分发到setTimeout队列中去,但是该任务就得等到下一轮事件循环执行。
<div id="div">
begin
</div>setTimeout(function() {
// 应该是这里执行前开始渲染ui,试试用alert阻塞下。
alert(' ui 已经渲染完毕了吗? ');
console.log('timeout1');
})
new Promise(function(resolve) {
console.log('promise1');
for(var i = 0; i < 1000; i++) {
i == 99 && resolve();
}
console.log('promise2');
}).then(function() {
console.log('then1');
alert(' ui 开始渲染 ');
})
console.log('global1');
div.innerHTML = 'end';上述代码中修改了div的内容,那么在执行那句js代码之后渲染引擎开始修改div的内容呢?
根据HTML Standard,一轮事件循环执行结束之后,下轮事件循环执行之前开始进行UI render。即:macro-task任务执行完毕,接着执行完所有的micro-task任务后,此时本轮循环结束,开始执行UI render。UI render完毕之后接着下一轮循环。
在chrome浏览器中执行以上代码,控制台先输出promise1,promise2,global1,then1(micro-task任务输出),弹出'ui 开始渲染'警告框,点击确定之后,页面中的'begin'变为'end',再弹出警告框'ui 已经渲染完毕了吗?' ,点击确认之后再输入timeout1.
<div class="outer" style="width:200px;height:200px;background-color: #ccc">
1
<div class="inner" style="width:100px;height:100px;background-color: #ddd">begin</div>
</div>// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
var i = 0;
// Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
console.log('mutate');
}).observe(outer, {
attributes: true
});
// Here's a click listener…
function onClick() {
i++;
if(i === 1) {
inner.innerHTML = 'end';
}
console.log('click');
setTimeout(function() {
alert('锚点');
console.log('timeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise');
});
outer.setAttribute('data-random', Math.random());
}
// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);当我们点击 inner div 时程序依次的执行顺序是:
此时,由于用户点击事件onclick产生的macrotask执行完毕,JS stack 清空,开始执行microtask.
此时,microtask 执行完毕,JS stack 清空,但是由于事件冒泡,接着执行outer上的onclick事件.
此时,由于outer上的onclick事件产生的macrotask执行完毕,JS stack 清空,开始执行microtask.
此时,本轮事件循环结束,UI 开始 render.
此时,UI render 完毕,开始下一轮事件循环.
到此为止,整个事件执行完毕,我们可以看到在弹出警告框之前inner的内容已经改变。
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
inner.click();此时的执行顺序是:
此时,inner 的 onclick 已经出 JS stack,但是script(整体代码)还没有出 JS stack,还不能执行microtask,由于冒泡,接着执行 outer 的 onclick.
接着执行的outer.setAttribute('data-random', Math.random());,但是由于上一个mutation microtask还处于等待状态,不能再添加mutation microtask,所以这里不会将 mutate 压入到 microtask。接着执行:
此时,inner.click()执行完毕,script(整体代码)已出 JS stack,JS stack 清空,开始执行mircotask.
此时,所有的mircotask执行完毕,本轮事件循环结束,UI 开始 render.
此时,UI render 完毕,开始下一轮事件循环.
到此为止,整个事件执行完毕,我们可以看到在弹出警告框之前inner的内容已经改变。
总结:首先执行macrotask,当js stack为空时执行microtask,接着开始UI render,接着再开始下一轮循环
参考文献:
深入核心,详解事件循环机制
Tasks, microtasks, queues and schedules