什么是宏任务

  • 我们可以将每次执行栈执行的代码当做是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
  • 每一个宏任务会从头到尾执行完毕,不会执行其他。

我们知道 JS引擎线程和 GUI渲染线程是互斥的关系,浏览器为了能够使 宏任务和 DOM任务有序的进行,会在一个 宏任务执行结果后,在下一个 宏任务执行前, GUI渲染线程开始工作,对页面进行渲染。

1
// 宏任务-->渲染-->宏任务-->宏任务-->渲染..

主代码块,setTimeoutsetInterval等,都属于宏任务

第一个例子:

我们可以将这段代码放到浏览器的控制台执行以下,看一下效果:

1
2
3
4
document.body.style = 'background:black';
document.body.style = 'background:red';
document.body.style = 'background:blue';
document.body.style = 'background:grey';

img

我们会看到的结果是,页面背景会在瞬间变成白色,以上代码属于同一次 宏任务,所以全部执行完才触发 页面渲染,渲染时 GUI线程会将所有UI改动优化合并,所以视觉效果上,只会看到页面变成灰色

第二个例子:

1
2
3
4
document.body.style = 'background:blue';
setTimeout(() => {
document.body.style = 'background:black';
}, 0)

img

我会看到,页面先显示成蓝色背景,然后瞬间变成了黑色背景,这是因为以上代码属于两次 宏任务,第一次 宏任务执行的代码是将背景变成蓝色,然后触发渲染,将页面变成蓝色,再触发第二次宏任务将背景变成黑色

什么是微任务

  • 我们已经知道 宏任务结束后,会执行渲染,然后执行下一个 宏任务,
  • 而微任务可以理解成在当前 宏任务执行后立即执行的任务。
  • 也就是说,当 宏任务执行完,会在渲染前,将执行期间所产生的所有 微任务都执行完

Promiseprocess.nextTick等,属于 微任务。

第一个例子:

1
2
3
4
5
6
7
document.body.style = 'background:blue';
console.log(1);
Promise.resolve().then(() => {
console.log(2)
document.body.style = 'background:black'
});
console.log(3)

执行后看效果:

img

  • 控制台输出 1 3 2 , 是因为 promise 对象的 then 方法的回调函数是异步执行,所以 2 最后输出
  • 页面的背景色直接变成黑色,没有经过蓝色的阶段,是因为,我们在宏任务中将背景设置为蓝色,但在进行渲染前执行了微任务,在微任务中将背景变成了黑色,然后才执行的渲染

第二个例子:

1
2
3
4
5
6
7
8
9
10
setTimeout(() => {
console.log(1)
Promise.resolve(3).then((data) => {
console.log(data)
}, 0)
})
setTimeout(() => {
console.log(2)
}, 0)
//1 3 2

执行结果如下:

image-20210412162949126

上面代码共包含两个 setTimeout ,也就是说除主代码块外,共有两个 宏任务, 其中第一个 宏任务执行中,输出 1 ,并且创建了 微任务队列,所以在下一个 宏任务队列执行前,先执行 微任务,在 微任务执行中,输出 3 ,微任务执行后,执行下一次 宏任务,执行中输出 2

总结

  • 执行一个 宏任务(栈中没有就从 事件队列中获取)
  • 执行过程中如果遇到 微任务,就将它添加到 微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前 微任务队列中的所有 微任务(依次执行)
  • 当前 宏任务执行完毕,开始检查渲染,然后 GUI线程接管渲染
  • 渲染完毕后, JS线程继续接管,开始下一个 宏任务(从事件队列中获取)

image-20210412162902064