We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
面试题:浏览器和 Node.js 的事件循环有什么区别?
上面这个问题是一个非常经典的面试题。 这篇文章会以这个面试题为起点,讲解浏览器的事件循环以及 Node.js 的事件循环
当我们使用浏览器的时候,每打开一个 Tab 其实就是打开了一个进程,这个进程下面的多个线程相互配合,一个浏览器通常包含这些线程
通过上文的描述可以看到,定时触发器线程、事件触发线程、异步请求线程最后干的事情都是将一个任务放到浏览器任务队列的队尾,等待 JS 线程执行
由此可见浏览器的事件循环说白了就是 macrotask 和 microtask 来回切换,每一轮循环中,执行一个 macrotask,等执行结束后,清空 microtask 队列中所有的微任务,然后再进入下一个事件循环
console.log('start') setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') }) }, 0) setTimeout(() => { console.log('timer2') Promise.resolve().then(function() { console.log('promise2') }) }, 0) Promise.resolve().then(function() { console.log('promise3') }) console.log('end')
在浏览器环境中,最终输出是 start end promise3 timer1 promise1 timer2 promise2
start end promise3 timer1 promise1 timer2 promise2
然而,在 Node 环境中,输出是 start end promise3 timer1 timer2 promise1 promise2
start end promise3 timer1 timer2 promise1 promise2
为什么会这样呢!
Node.js 采用 V8 作为 js 的解析引擎,在 IO 方面使用了自己设计的 libuv,封装了不同操作系统一些底层的特性,并且对外提供统一的 API,事件循环机制也是在 libuv 中实现。
Node.js 的运行机制如下
而事件循环分为6个阶段,每一个阶段都保留一个 FIFO 的回调队列,当进入到任意一个阶段的时候,都会从对应的回调队列取出函数来执行 —— 当队列为空或者执行的函数数量到达系统设定的阈值,就会进入下一个阶段
setImmediate()
check 阶段的主要作用就是执行 setImmediate()。说白了 setImmediate 就是一个特殊的 timer,它单独的存在于事件循环的一个阶段。
一般来说,当代码运行的时候,event loop 最终会停留在 poll 阶段,等待新的 connection、request、data 等。但是,如果任意一个 callback 调度了 setImmediate() 且 poll phase 空闲了,那么就会进入到 check 阶段,而不是继续等待
setTimeout
setImmediate
其实 setImmediate 就是一个更加特殊的 timer,那么二者谁会先执行呢?
如果二者都在主模块里
setTimeout(() => console.log("setTimeout"), 0) setImmediate(() => console.log("setImmediate"))
那么执行的先后顺序不一定!
setTimeout(fn, 0)
setTimeout(fn, 1)
setTimeout(fn, 4)
但是,如果已经进入了 eventloop 呢?
var fs = require('fs') fs.readFile(__filename, () => { setTimeout(() => { console.log('setTimeout'); }, 0); setImmediate(() => { console.log('setImmediate'); }); });
此时一定是先执行 setImmediate 的!
另外,想要提到的一点是,如果你在 setTimeout 函数里面增加了一个新的 setTimeout(fn2),那么这个 fn2 是会被放在下一次 timers phase 里进行处理的。同理,你在 setImmediate 里面增加一个 setImmediate,新的 setImmediate 内容也是会在下一次 check 阶段处理。
process.nextTick()
这个函数独立于 event loop 之外,有自己的队列。当每个阶段结束后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且 nextTick 中的回调函数优先于其他的 microtask 执行
浏览器中是每处理一个 macrotask,就要清空所有的 microtask 队列中的 microtask
而 node 中是每处理完一个阶段的 macrotask,再去清空所有的 microtask 队列中的 microtask
这就是上面的例子中差别的原因,Node.js 里面要把 timers 阶段中多个回调函数都处理完了,再去处理 microtask
但是,这个特性在 node 11 版本后被改了一下!
node 11 之后,node 的表现与浏览器一致,也是每个 macrotask (setTimeout setInterval setImmediate) 处理完了,就检查一次 microtask
The text was updated successfully, but these errors were encountered:
No branches or pull requests
Node.js 事件循环
上面这个问题是一个非常经典的面试题。
这篇文章会以这个面试题为起点,讲解浏览器的事件循环以及 Node.js 的事件循环
浏览器内核
当我们使用浏览器的时候,每打开一个 Tab 其实就是打开了一个进程,这个进程下面的多个线程相互配合,一个浏览器通常包含这些线程
通过上文的描述可以看到,定时触发器线程、事件触发线程、异步请求线程最后干的事情都是将一个任务放到浏览器任务队列的队尾,等待 JS 线程执行
由此可见浏览器的事件循环说白了就是 macrotask 和 microtask 来回切换,每一轮循环中,执行一个 macrotask,等执行结束后,清空 microtask 队列中所有的微任务,然后再进入下一个事件循环
在浏览器环境中,最终输出是
start end promise3 timer1 promise1 timer2 promise2
然而,在 Node 环境中,输出是
start end promise3 timer1 timer2 promise1 promise2
为什么会这样呢!
Node 中的 EventLoop
Node.js 采用 V8 作为 js 的解析引擎,在 IO 方面使用了自己设计的 libuv,封装了不同操作系统一些底层的特性,并且对外提供统一的 API,事件循环机制也是在 libuv 中实现。
Node.js 的运行机制如下
而事件循环分为6个阶段,每一个阶段都保留一个 FIFO 的回调队列,当进入到任意一个阶段的时候,都会从对应的回调队列取出函数来执行 —— 当队列为空或者执行的函数数量到达系统设定的阈值,就会进入下一个阶段
timers
poll
setImmediate()
那么 poll 阶段直接结束,进入到 check 阶段,然后执行setImmediate()
setImmediate()
,eventLoop 会在 poll 阶段等待一段时间,如果有新的输入的回调函数,那么会立即执行check
check 阶段的主要作用就是执行
setImmediate()
。说白了 setImmediate 就是一个特殊的 timer,它单独的存在于事件循环的一个阶段。一般来说,当代码运行的时候,event loop 最终会停留在 poll 阶段,等待新的 connection、request、data 等。但是,如果任意一个 callback 调度了 setImmediate() 且 poll phase 空闲了,那么就会进入到 check 阶段,而不是继续等待
setTimeout
vesetImmediate
其实 setImmediate 就是一个更加特殊的 timer,那么二者谁会先执行呢?
如果二者都在主模块里
那么执行的先后顺序不一定!
setTimeout(fn, 0)
会被 Node.js 强制转换成setTimeout(fn, 1)
[浏览器中是setTimeout(fn, 4)
]但是,如果已经进入了 eventloop 呢?
此时一定是先执行 setImmediate 的!
另外,想要提到的一点是,如果你在 setTimeout 函数里面增加了一个新的 setTimeout(fn2),那么这个 fn2 是会被放在下一次 timers phase 里进行处理的。同理,你在 setImmediate 里面增加一个 setImmediate,新的 setImmediate 内容也是会在下一次 check 阶段处理。
process.nextTick()
这个函数独立于 event loop 之外,有自己的队列。当每个阶段结束后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且 nextTick 中的回调函数优先于其他的 microtask 执行
总结 —— Node.js 与浏览器的差异
浏览器中是每处理一个 macrotask,就要清空所有的 microtask 队列中的 microtask
而 node 中是每处理完一个阶段的 macrotask,再去清空所有的 microtask 队列中的 microtask
这就是上面的例子中差别的原因,Node.js 里面要把 timers 阶段中多个回调函数都处理完了,再去处理 microtask
但是,这个特性在 node 11 版本后被改了一下!
node 11 之后,node 的表现与浏览器一致,也是每个 macrotask (setTimeout setInterval setImmediate) 处理完了,就检查一次 microtask
The text was updated successfully, but these errors were encountered: