面试题答案
一键面试1. 事件循环的具体实现
- 浏览器环境:
- 浏览器的事件循环是基于HTML5规范实现的。事件循环会不断检查调用栈是否为空,当调用栈为空时,从宏任务队列中取出一个任务放入调用栈执行。执行完该宏任务后,会检查微任务队列,将微任务队列中的所有任务依次执行完,然后再从宏任务队列中取下一个任务,如此循环。
- 例如,在浏览器中执行一段JavaScript代码,首先会将同步代码放入调用栈执行,遇到异步任务(如
setTimeout
)会将其放入宏任务队列,遇到微任务(如Promise.then
)会将其放入微任务队列。
- Node.js环境:
- Node.js的事件循环基于libuv库实现。它有多个阶段,每个阶段都有自己的任务队列。事件循环首先进入
timers
阶段,检查并执行setTimeout
和setInterval
设定的回调函数(属于宏任务)。然后进入I/O callbacks
阶段,执行一些系统I/O的回调。接着是idle, prepare
阶段(内部使用),再到poll
阶段,这个阶段会等待新的I/O事件,处理I/O队列中的事件。之后是check
阶段,执行setImmediate
的回调(宏任务)。最后是close callbacks
阶段,执行一些关闭的回调。在每个阶段执行完后,都会检查并执行微任务队列中的任务。
- Node.js的事件循环基于libuv库实现。它有多个阶段,每个阶段都有自己的任务队列。事件循环首先进入
2. 宏任务和微任务的种类及优先级
- 宏任务种类:
- 浏览器环境:常见的宏任务包括
setTimeout
、setInterval
、requestAnimationFrame
、I/O操作
(如fetch
等网络请求完成后的回调)、UI渲染
(当有页面更新等情况时)。 - Node.js环境:除了
setTimeout
、setInterval
,还有setImmediate
、I/O操作
(文件系统操作、网络操作等完成后的回调)。其中setImmediate
的优先级在poll
阶段之后,setTimeout
和setInterval
的优先级在timers
阶段。
- 浏览器环境:常见的宏任务包括
- 微任务种类:
- 浏览器环境:主要是
Promise.then
、MutationObserver
(用于监听DOM变化)等。 - Node.js环境:同样有
Promise.then
,还有process.nextTick
。process.nextTick
的优先级高于Promise.then
,它会在当前操作完成后,事件循环的下一个阶段之前执行,而Promise.then
是在当前阶段的所有同步任务执行完后执行。
- 浏览器环境:主要是
- 优先级:
- 浏览器环境:微任务优先级高于宏任务,即每次宏任务执行完后,会先清空微任务队列再执行下一个宏任务。
- Node.js环境:微任务同样优先于宏任务执行,但在微任务内部,
process.nextTick
优先于Promise.then
。不同阶段的宏任务优先级也有所不同,例如setImmediate
在poll
阶段之后执行,而setTimeout
在timers
阶段执行。
3. 对跨环境开发的影响及应对策略
- 影响:
- 代码执行顺序差异:由于事件循环和任务优先级的不同,同样的代码在浏览器和Node.js中可能执行顺序不同。例如,在浏览器中
setTimeout
和Promise.then
的执行顺序相对固定,而在Node.js中,process.nextTick
和Promise.then
以及不同宏任务之间的执行顺序因阶段而异,这可能导致依赖特定执行顺序的代码在不同环境下出现问题。 - 任务类型差异:Node.js有
setImmediate
和process.nextTick
等独特的任务类型,浏览器没有。浏览器有requestAnimationFrame
和MutationObserver
等Node.js不存在的任务类型。如果代码中使用了这些特定环境的任务类型,直接跨环境运行会报错。
- 代码执行顺序差异:由于事件循环和任务优先级的不同,同样的代码在浏览器和Node.js中可能执行顺序不同。例如,在浏览器中
- 应对策略:
- 避免依赖特定执行顺序:编写代码时尽量避免依赖任务的精确执行顺序,使代码逻辑更加松散耦合。例如,不要在不同类型任务回调中依赖前一个任务回调的执行结果顺序,而是通过数据共享等方式来协调任务。
- 使用通用任务类型:尽量使用在浏览器和Node.js环境中都存在且行为一致的任务类型,如
Promise
。对于需要定时执行的任务,可以统一使用setTimeout
。如果需要类似setImmediate
的功能,在浏览器中可以模拟实现。 - 条件编译:对于必须使用特定环境任务类型的情况,可以通过条件编译来区分不同环境。例如,使用
process.env.NODE_ENV
来判断是否在Node.js环境中,然后分别引入不同的代码逻辑。在浏览器环境中,针对requestAnimationFrame
和MutationObserver
等功能,可以提供Polyfill,以便在不支持的环境中也能使用类似功能。