事件循环
micro task 和 macro task 表示异步任务的两种分类。
js语言为单线程,同一个时间只能做一件事,为了防止主线程的不阻塞,Event Loop 的方案应用而生。
Event Loop 包含两类:一类是基于 Browsing Context,一种是基于 Worker。二者的运行是独立的,也就是说,每一个 JavaScript 运行的"线程环境"都有一个独立的 Event Loop,每一个 Web Worker 也有一个独立的 Event Loop。
任务队列
同步任务在函数调用栈内执行,异步任务则需要借助任务队列,一个线程中调用栈只有一个,但任务队列可以有多个;
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的关键步骤如下:
- 在此次 tick 中选择最先进入队列的任务(oldest task),如果有则执行(一次)
- 检查是否存在 Microtasks,如果存在则不停地执行,直至清空 Microtasks Queue
- 更新 render
- 主线程重复执行上述步骤
宏任务
setTimeout、setInterval、Ajax、DOM事件等
微任务
Promise、async / await等
运行机制
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前微任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
如图:
一、什么是微任务?什么是宏任务?
- 微任务:Promise、async / await 等
- 宏任务:setTimeout、setInterval、Ajax、DOM事件等
- 微任务执行时机早于宏任务(具体原因下面会说到)
二、event loop 和 DOM 渲染?
js 是单线程的,js 的执行和 DOM 渲染共用一个线程,所以 js 执行需要留一些时机给 DOM 渲染。
执行顺序:
1、执行同步代码,执行完毕调用栈清空,
2、触发微任务
3、尝试 DOM 渲染(有内容更新就渲染,无更新忽略)
4、触发 event loop,执行宏任务
三、微任务、宏任务和 DOM 渲染的关系
先后顺序:微任务 ---> DOM渲染 ---> 宏任务
三、微任务、宏任务和 DOM 渲染,在 event loop 的过程
微任务:DOM 渲染前触发
宏任务:DOM 渲染后触发
const span = $('<span>这是添加的元素</span>')
$('#content').append(span);
// 微任务
Promise.resolve().then(() => {
alert('Promise then'); // 1 DOM 渲染了吗? --否
})
// 宏任务
setTimeout(() => {
alert('setTimeout'); // 2 DOM 渲染了吗? --是
})
为什么微任务执行更早?
微任务和宏任务的队列是分开的,微任务是 ES6 语法规定的,宏任务是浏览器规定的,在 ES6 规范里是没有 setTimeout、onclick事件、ajax方法等,所以它们存放的地址是不一样的。
Promise 放在微任务队列里(micro task queue),它不经过 Web API,因为 Promise 是 ES 规范,不是 W3C 规范。
下面举个例子,也是常见的面试题目
async function async1() {
console.log('async1 start'); // 2
await async2();
// await 后面都是异步 -- 微任务1
console.log('async1 end'); // 6
}
async function async2() {
console.log('async2'); // 3
}
console.log('script start'); // 1
// 异步 -- 宏任务1
setTimeout(() => {
console.log('setTimeout'); // 8
}, 0)
async1();
// Promise 初始化时候的代码 立即执行
new Promise((resolve) => {
console.log('Promise1'); // 4
resolve();
}).then(() => { // 异步 -- 微任务2
console.log('Promise2'); // 7
})
console.log('script end'); // 5
/**
* 到此所有的同步代码执行完毕(call stack 被清空),开始异步 触发 event loop
* 执行微任务
* 尝试 DOM 渲染
* 执行宏任务
*
* 打印顺序:
* script start
* async1 start
* async2
* Promise1
* script end
* async1 end
* Promise2
* setTimeout
*/