浏览器中的事件可以分为两种:同步事件 和 异步事件
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
浏览器的事件循环有以下几个步骤:(通俗讲就是先执行同步任务,后执行异步任务)
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
关于任务队列,任务队列又分为 宏任务队列 和 微任务队列
宏任务:setTimeout、setInterval、script(整个 js 文件代码)、 I/O 操作、UI 渲染等。
微任务:Promise 中的 .then, MutationObserver(html5新特性)
而任务队列又是如何循环的?
1.最开始, 执行栈为空, 微任务队列为空, 宏任务队列有一个 script 标签(内含整体代码)
2.将第一个宏任务出队, 这里即为上述的 script 标签
3.整体代码执行过程中,
如果是同步代码, 直接执行(函数执行的话会有入栈出栈操作),
如果是异步代码, 会根据任务类型推入不同的任务队列中(宏任务或微任务)
4.当执行栈执行完为空时, 会去处理微任务队列的任务, 将微任务队列的任务一个个推入调用栈执行完
5.调用栈为空后, 再出队一个宏任务, 推入调用栈执行
6.调用栈为空时, 去执行一队微任务
7…往返循环直到宏任务和微任务队列为空
我们可以通过一段代码来看一下
console.log('同步任务开始')
Promise.resolve().then(() => {
console.log('Promise1')
setTimeout(() => {
console.log('setTimeout2')
}, 0)
})
setTimeout(() => {
console.log('setTimeout1')
Promise.resolve().then(() => {
console.log('Promise2')
})
Promise.resolve().then(() => {
console.log('Promise3')
})
}, 0)
console.log('同步任务结束')
输出结果如下:
可以分析一下这段代码
1.script标签(全部代码入栈)
2.执行同步代码,即两句打印语句,并将两句异步任务代码即.then()方法放入微任务队列,setTimeout()方法放入宏任务队列
3.执行微任务队列的所有任务, 这里打印出 “Promise1”,同时又将里面的异步任务 setTimeout 推入宏任务队列中
4.取出第一个宏任务推入执行栈执行, 打印出 “setTimeout1”, 同时将内部的异步任务 promise.resolve().then 推入微任务队列
5.执行微任务队列中的所有任务, 先后打印出 “Promise2"和"Promise3”
6.执行最后一个宏任务, 打印出 “setTimeout2”