1、什么是事件循环
事件循环就是消息队列,是浏览器渲染主线程的工作方式;
过去将消息队列,简单的分为宏任务
和微任务
两种队列,而对于现在复杂多变的浏览器环境,显然这种处理方式已经不能满足使用,取而代之的是一种更加灵活多变的处理方式;
W3C给出的解释是:
每个任务有不同的类型,相同类型的任务会被放入同一个执行队列中,不同的任务可以隶属于不同的队列,不同的任务队列有不同的执行优先级
,在一次事件循环中,浏览器自行决定取哪一个任务队列,但是浏览器必须有一个微任务队列,并且微任务队列中的任务执行级别最高,优先执行微任务;
宏任务中的计时器 是不能达到精准执行的,即使在当前执行的队列中没有其他任务执行时,当前任务的执行也是有略微的时间差别
如: setTimeout(() => {}, 5000)
a、只能说该任务最小执行时间为 5000
毫秒后执行,因为 js
执行的计时器是调用的浏览器函数,而浏览器函数的执行也是需要时间的;
b、若当前任务之前有其他任务,需要等待其他任务执行完成之后才能执行该任务,这个时间是大于 5000 毫秒
的
c、计时器需要 相同的任务队列 执行完毕,即空任务时候才能去执行计时器;
2、同步任务 异步任务
JavaScript
是一门单线程语言,但是单线程并不意味着阻塞
。实现单线程非阻塞的方式就是事件循环机制。在JavaScript
中,所有的事件都可以分为同步任务
和异步任务
。
同步任务:立即执行的任务。同步任务一般会直接进入到主线程执行。
异步任务:
比如ajax
请求、
setTimeout
定时器,
回调函数、
事件驱动(Event-Driven
)、
观察者模式(Observer pattern
)|发布订阅模式(publish-subscribe pattern
)
Promise,async/await
;
宏任务(定时器,ajax, DOM
事件监听);
setImmediate
(立即执行,Node.js
执行环境);
mutationobserver
(H5
, 监视DOM
元素变化)
等。
任务进入执行栈,会先判断当前任务是同步任务
还是异步任务
,如果是同步任务则会进入到主线程,立即执行;异步任务
会先放到Event Table
,注册回调函数到Event Queue
。等待所有的同步任务执行完后
,主线程会去Event Queue
中读取异步任务
到主线程中执行,这个过程的不断重复就是事件循环。
3、宏任务 微任务
微任务:
常见的有如下:Promise.then
、MutationObserver()
、Object.observe(已废弃,被Proxy对象替代)
、process.nextTick
。
MutationObserver
接口提供了监视对 DOM 树所做更改的能力 详见:
说明:process.nextTick()
是Node.js
中的一个API
,用于将一个回调函数添加到事件循环的下一轮迭代中执行。它的作用类似于setImmediate
,但是它的优先级要比setImmediate()
更高,会尽快执行。通常用于在当前事件循环结束后立即执行一些操作,而不需要等待下一轮事件循环。
宏任务:
宏任务的时间粒度比较大
,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合。
常见的宏任务有:script(外层同步代码)
、setTimeout/setInterval
、UI rendering/UI事件渲染
、postMessage
、MessageChannel
、setImmediate
、I/O(Node.js)
执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中;当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完。
可以看下下面的执行顺序是否跟自己想的一样
console.log("1");
setTimeout(() => {
console.log("2");
new Promise<void>(resolve => {
console.log("3");
resolve();
}).then(() => {
console.log("4");
});
}, 0);
new Promise<void>(resolve => {
console.log("5");
resolve();
}).then(() => {
console.log("6");
setTimeout(() => {
console.log("7");
}, 0);
});
// 运行结果 : 1 5 6 2 3 4 7