什么是事件循环
是单线程的js在处理异步事件时进行的一种循环过程.也就是说发生异步事件的时候,这些事件会被加入到事件队列中挂起,等待主线程空闲的时候去执行.一旦主线程空闲,就会从事件队列中取出事件进行处理.处理完一个,再取出下一个事件进行处理,如此反复循环
事件循环解决了什么
让js可以在单线程中处理异步操作,避免了阻塞,这种机制在处理大量异步事件时,能够保持程序的稳定性
事件循环 -> 任务队列
过去把消息队列简单分为宏任务和微任务,但是随着浏览器复杂度急剧提升,W3C已经不再使用宏任务的说法
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就一直等着。
在 JavaScript中任务分为同步与异步任务,其中异步任务又分为两种:宏任务(Macro Task)和 微任务(Micro Task)。同步任务(synchronous),异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
常见宏任务:
- script标签中的代码
- setTimeout
- setInterval
- setImmediate(Node.js)
- IO
- UI 渲染
- MessageChannel
常见微任务:
- Promise.then(非 new Promise)
- async
- await
- Object.observe
- MutationObserver
宏任务和微任务的执行顺序:总方针是先同步再异步,异步中先微任务,在宏任务
process.nextTick(Node.js)
在 Node.js 环境中,process.nextTick 的回调函数会在微任务队列中的其他任务之前执行,因此在输出顺序上会比 Promise.then 更早。而在浏览器环境中,nextTick 的回调函数会在当前微任务队列中的其他任务执行完毕后立即执行,因此在输出顺序上会比 Promise.then 更晚。
W3C最新的事件循环
每个任务都有一个任务类型,同一个类型的任务必须在一个队列里,不同类型的任务分属于不同的队列,在一次事件循环中,浏览器可以根据实际情况从不同队列中取出任务执行。 大概有以下几个的队列DOM操作
、用户交互
、网络请求
、网页导航和历史记录遍历
、渲染
同时浏览器必须准备好一个微队列,微队列中的任务优先于所有其他任务执行
目前的Chrome实现中,至少包含了以下队列:
队列类型 | 描述 | 优先级 |
---|---|---|
微队列 | 用于存放需要最快执行的任务 | 最高 |
交互队列 | 用于存放用户操作后产生的事件处理任务 | 高 |
延时队列 | 用于存放计时器到达后的回调任务 | 中 |
网络队列 | 用于处理网络活动产生的任务 | 中 |
在W3C中对于事件循环(Event Loop)的处理已经不再是之前的宏任务与微任务的方式了,每个任务都有一个任务类型,同一个类型的任务必须在一个队列里,不同类型的任务分属于不同的队列,在一次事件循环中,浏览器可以根据实际情况从不同队列中取出任务执行。但是浏览器也必须准备好一个微队列(microtask),微队列中的任务优先于所有其他任务执行。
事件循环导致的setTimeout有误差
在js中,setTimeout不是在指定的延迟时间后立即执行回调,而是将回调放入事件循环的队列中,等待主线程空闲的时候执行,但是实际执行的事件可能会收到任务队列中其他任务的影响,导致延迟,就比如指定的延迟时间到了,但是主线程还有一个网络请求等等,那么setTimeout就会被放到任务队列中等待主线程空闲在执行了