MST

星途 面试题库

面试题:JavaScript 定时器与事件循环的深度剖析

深入探讨 JavaScript 中 setTimeout、setInterval 与事件循环(Event Loop)之间的关系。当存在多个不同延迟时间的 setTimeout 和 setInterval 任务时,事件循环是如何调度执行它们的?请结合底层原理详细说明,并且分析在不同浏览器环境下可能存在的差异及其原因。
19.8万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

1. JavaScript 中的事件循环(Event Loop)原理

  • JavaScript 是单线程语言,这意味着它在同一时间只能执行一个任务。事件循环机制的作用就是协调任务执行,使得 JavaScript 可以在单线程环境下处理异步操作。
  • 事件循环主要涉及三个概念:调用栈(Call Stack)、任务队列(Task Queue,也叫宏任务队列 Macrotask Queue)和微任务队列(Microtask Queue)。
    • 调用栈:是一个存储函数调用的栈结构,函数调用时被压入栈,执行完毕后从栈中弹出。
    • 任务队列:当异步任务(如 setTimeoutsetInterval 回调,DOM 事件回调等)完成时,其回调函数会被放入任务队列。
    • 微任务队列:包含一些需要在当前任务执行结束后尽快执行的任务,如 Promise.thenMutationObserver 回调等。
  • 事件循环的基本流程如下:
    1. 检查调用栈是否为空,如果为空,则从任务队列中取出一个任务(宏任务)放入调用栈执行。
    2. 在执行当前宏任务过程中,可能会产生新的宏任务和微任务,新产生的微任务会被放入微任务队列。
    3. 当前宏任务执行完毕后,调用栈再次为空,此时事件循环会将微任务队列中的所有微任务依次放入调用栈执行,直到微任务队列清空。
    4. 重复上述过程,不断从任务队列中取出宏任务执行,并在宏任务执行结束后处理微任务队列。

2. setTimeout 和 setInterval 与事件循环的关系

  • setTimeout
    • setTimeout 函数用于在指定的延迟时间(毫秒)后将回调函数放入任务队列。例如,setTimeout(callback, delay)delay 是延迟时间,callback 是要执行的回调函数。
    • 即使延迟时间设置为 0,setTimeout 的回调函数也不会立即执行,而是会被放入任务队列等待事件循环调度。这是因为事件循环需要先处理完当前调用栈中的任务和微任务队列中的任务,才会从任务队列中取出 setTimeout 的回调执行。
  • setInterval
    • setInterval 函数用于按照指定的时间间隔(毫秒)重复地将回调函数放入任务队列。例如,setInterval(callback, interval)interval 是时间间隔,callback 是每次间隔后要执行的回调函数。
    • 每次间隔时间到达时,setInterval 会将回调函数放入任务队列,但如果前一个回调函数还在执行(调用栈不为空),新放入的回调函数只能在任务队列中等待。而且,setInterval 不会考虑回调函数的执行时间,如果回调函数执行时间较长,可能会导致回调函数执行间隔不准确。

3. 多个不同延迟时间的 setTimeout 和 setInterval 任务的调度执行

  • 当存在多个不同延迟时间的 setTimeoutsetInterval 任务时:
    • setTimeout:延迟时间短的 setTimeout 回调函数会先被放入任务队列,但不一定先执行。因为事件循环是按照调用栈和任务队列的机制工作的,只有当前调用栈为空且微任务队列清空后,才会从任务队列中取出宏任务执行。所以如果有其他宏任务(如更早放入任务队列的 setTimeout 回调或其他 DOM 事件回调等)正在执行或在任务队列中排队,延迟时间短的 setTimeout 回调可能需要等待。
    • setInterval:每个 setInterval 会按照自己设定的时间间隔将回调函数放入任务队列。不同 setInterval 之间的回调放入任务队列的顺序不受彼此影响,但执行顺序同样取决于事件循环机制,要等待调用栈为空且微任务队列清空。如果不同 setInterval 的回调执行时间较长,可能会相互影响执行间隔的准确性。
    • 混合情况:例如有 setTimeout A(延迟 100ms)、setTimeout B(延迟 200ms)和 setInterval C(间隔 300ms)。setTimeout A 的回调会先于 setTimeout B 的回调放入任务队列,但如果 setInterval C 的回调已经在任务队列中且在 setTimeout A 的回调之前被调度执行,setTimeout A 的回调就需要等待。

4. 不同浏览器环境下的差异及其原因

  • 最小延迟差异
    • 不同浏览器对 setTimeoutsetInterval 的最小延迟时间有不同的限制。例如,在 HTML5 标准中规定,setTimeoutsetInterval 的最小延迟时间为 4ms,但在实际中,有些浏览器可能会有不同的默认值。这是因为不同浏览器为了性能和兼容性等方面的考虑,对最小延迟进行了不同的设置。
    • 一些浏览器可能会将最小延迟设置得更高,以避免频繁地触发回调函数导致性能问题。例如,某些浏览器可能将最小延迟设置为 10ms 或 16.67ms(大约每秒 60 次,与屏幕刷新率匹配,以优化动画性能)。
  • 执行间隔准确性差异
    • 在处理 setInterval 时,不同浏览器对回调函数执行间隔的准确性也存在差异。由于 JavaScript 的单线程特性以及浏览器内部的任务调度机制,setInterval 的回调执行间隔可能并不完全准确。
    • 一些浏览器可能会对 setInterval 进行优化,尽量保证回调函数按照设定的间隔执行,例如通过调整任务队列的调度策略。而另一些浏览器可能由于内部实现的复杂性,在处理复杂场景(如页面中有大量其他任务同时执行)时,setInterval 的执行间隔偏差会更大。
  • 任务队列优先级差异
    • 不同浏览器对任务队列中不同类型任务的优先级设置可能不同。例如,某些浏览器可能会优先处理与用户交互相关的事件(如点击事件),而将 setTimeoutsetInterval 的回调任务放在相对较低的优先级。这会导致在高负载情况下,setTimeoutsetInterval 的回调执行可能会被延迟,不同浏览器的延迟程度可能有所不同,取决于其具体的优先级设置和调度算法。