事件循环
> js 是单线程,js 引擎在执行时的原则:获取任务、执行任务。反复重复此过程,直到没有可执行的任务为止。任务分为同步任务和异步任务。异步任务分为宏任务和微任务。
- js 处理异步主要有微任务(microTask)和 宏任务 (macroTask),而从开始执行一个宏任务–>执行完这个宏任务中所有同步代码—>清空当前微任务队列中所有微任务—> UI 渲染 。 这便是完成了一个事件循环(Tick), 然后开始执行下一个宏任务(相当于下一轮循环)。
宏任务
- script(整体代码)
- setTimeout、setInterval
- setImmediate(node)
- requestAnimationFrame
- I/O
- DOM
微任务
- process.nextTick(node)
- Promise.then、Promise.catch、Promise.finally
- MutationObserver(监听 dom 变化)
如何执行
> 首先执行宏任务,在执行宏任务过程中,遇到同步代码立即推入执行栈,遇到微任务追加到微任务队列,遇到宏任务追加到宏任务队列,等待同步任务执行完成后,将微任务队列中的任务依次执行直到微任务队列为空,到此循环结束,下一个循环从宏任务开始执行宏任务重复以上操作。
事件循环机制(node)
- timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调
- 定时器检测阶段(timers):本阶段执行 timer 的回调,即 setTimeout、setInterval 里面的回调函数
- I/O 事件回调阶段(I/O callbacks):执行延迟到下一个循环迭代的 I/O 回调,即上一轮循环中未被执行的一些 I/O 回调
- 闲置阶段(idle, prepare):仅系统内部使用
- 轮询阶段(poll):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞
- 检查阶段(check):setImmediate() 回调函数在这里执行
- 关闭事件回调阶段(close callback):一些关闭的回调函数,如:socket.on('close',...)
setTimeout 与 setInterval 时间不准如何解决
> 定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行。setTimeout 低于 4ms 时间间隔为 4ms(不同浏览器有不同的最小时间设定)
setTimeout(js 单线程阻塞,由于前一个任务耗时过长倒是定时器无法立即放入到事件队列)
- 确保回调函数尽量短,避免时间的计算或阻塞操作过长。
- 可以使用 requestAnimationFrame 代替 setInterval
- 使用 setImmediate 代替 setTimeout
setInterval(累计误差、单线程阻塞)
- 使用 setTimeout 代替 setInterval
- 计算每次回调执行的时间
为什么使用 setTimeout 代替 setInterval
- setInterval,某些时间会被跳过(比如上个还是队列中就会被跳过)
- setInterval 可能多个定时器连续触发(上一个在执行,下一个进入队列时间,上一个执行过长下一个直接执行)
- setTimeout 每次产生的任务直接 push 到任务队列,不会出现连续触发