事件循环
1.微任务
Node 中的“微任务”。这里说的微任务(microtasks)其实是一个统称,包含了两部分:
process.nextTick() 注册的回调 (nextTick task queue)
promise.then() 注册的回调 (promise task queue)
Node 在执行微任务时, 会优先执行 nextTick task queue 中的任务,执行完之后会接着执行 promise task queue 中的任务。所以如果 process.nextTick 的回调与 promise.then 的回调都处于主线程或事件循环中的同一阶段, process.nextTick 的回调要优先于 promise.then 的回调执行。
2.事件循环机制
如图,表示Node执行的整个过程。如果执行了任何非阻塞异步代码(创建计时器、读写文件等),则会进入事件循环。其中事件循环分为六个阶段:
由于Pending callbacks、Idle/Prepare 和 Close callbacks 阶段是 Node 内部使用的三个阶段,所以这里主要分析与开发者代码执行更为直接关联的Timers、Poll 和 Check 三个阶段。
Timers(计时器阶段):从图可见,初次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(包含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Pending callbacks 阶段。
Pending callbacks:执行推迟到下一个循环迭代的I / O回调(系统调用相关的回调)。
Idle/Prepare:仅供内部使用。(详略)
Poll(轮询阶段):
当回调队列不为空时:
会执行回调,若回调中触发了相应的微任务,这里的微任务执行时机和其他地方有所不同,不会等到所有回调执行完毕后才执行,而是针对每一个回调执行完毕后,就执行相应微任务。执行完所有的回到后,变为下面的情况。
当回调队列为空时(没有回调或所有回调执行完毕):
但如果存在有计时器(setTimeout、setInterval和setImmediate)没有执行,会结束轮询阶段,进入 Check 阶段。否则会阻塞并等待任何正在执行的I/O操作完成,并马上执行相应的回调,直到所有回调执行完毕。
Check(查询阶段):会检查是否存在 setImmediate 相关的回调,如果存在则执行所有回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Close callbacks 阶段。
Close callbacks:执行一些关闭回调,比如 socket.on(‘close’, …)等。
总结&注意:
每一个阶段都会有一个FIFO回调队列,都会尽可能的执行完当前阶段中所有的回调或到达了系统相关限制,才会进入下一个阶段。
Poll 阶段执行的微任务的时机和 Timers 阶段 & Check 阶段的时机不一样,前者是在每一个回调执行就会执行相应微任务,而后者是会在所有回调执行完之后,才统一执行相应微任务。
3.setImmediate、setTimeout/setInterval 和 process.nextTick 执行时机对比
setImmediate:触发一个异步回调,在事件循环的 Check 阶段立即执行。
setTimeout:触发一个异步回调,当计时器过期后,在事件循环的 Timers 阶段执行,只执行一次(可用 clearTimeout 取消)。
setInterval:触发一个异步回调,每次计时器过期后,都会在事件循环的 Timers 阶段执行一次回调(可用 clearInterval 取消)。
process.nextTick:触发一个微任务(异步)回调,既可以在主线程(mainline)中执行,可以存在事件循序的某一个阶段中执行。
事件循环中,每次打算执行一个回调之前,必须要先清空nextTick和promise队列,promis相当于vip,nextTick就相当与超级vip,其他的接受普通,都要按顺序,先执行超级vip,再执行vip,最后再是普通人员。
面试题(二)
1.对象的扁平化处理
const isObject = (val) => typeof val === "object" && val !== null
function flatten(obj) {
if (!isObject(obj)) return
const res = {}
const dfs = (cur, prefix) => {
if (isObject(cur)) {
if (Array.isArray(cur)) {
cur.forEach((item, index) => {
dfs(item, `${prefix}[${index}]`)
})
} else {
for(let key in cur) {
dfs(cur[key], `${prefix}${prefix ? '.' : ''}${key}`)
}
}
} else {
res[prefix] = cur
}
}
dfs(obj, '')
return res
}
// 测试
console.log(flatten(obj))
2.实现一个科里化函数
function currying(fn, ...args1) {
// 获取fn参数有几个
const length = fn.length
let allArgs = [...args1]
const res = (...arg2) => {
allArgs = [...allArgs, ...arg2]
// 长度相等就返回执行结果
if (allArgs.length === length) {
return fn(...allArgs)
} else {
// 不相等继续返回函数
return res
}
}
return res
}
// 测试:
const add = (a, b, c) => a + b + c;
const a = currying(add, 1);
console.log(a(2,3))