共计 2121 个字符,预计需要花费 6 分钟才能阅读完成。
明天在群里聊天,忽然有人放出了一道面试题。通过群里一番探讨,最终解题思路缓缓欠缺起来,我这里就整顿一下群内解题的思路。
该题定义了一个同步函数对传入的数组进行遍历乘二操作,同时每执行一次就会给 executeCount
累加。最终咱们须要实现一个 batcher
函数,应用其对该同步函数包装后,实现每次调用仍旧返回预期的二倍后果,同时还须要保障 executeCount
执行次数为 1。
let executeCount = 0
const fn = nums => {
executeCount++
return nums.map(x => x * 2)
}
const batcher = f => {// todo 实现 batcher 函数}
const batchedFn = batcher(fn);
const main = async () => {const [r1, r2, r3] = await Promise.all([batchedFn([1,2,3]),
batchedFn([4,5]),
batchedFn([7,8,9])
]);
// 满足以下 test case
assert(r1).tobe([2, 4, 6])
assert(r2).tobe([8, 10])
assert(r3).tobe([14, 16, 18])
assert(executeCount).tobe(1)
}
抖伶俐解法
拿到题目的第一工夫,我就想到了抖伶俐的办法。间接面向用例编程,执行完之后重置下 executeCount
就好了。
const batcher = f => {
return nums => {try { return f(nums) } finally {executeCount = 1}
}
}
当然除非你不在乎这次面试,否则个别不倡议你用这种抖伶俐的办法答复面试官(不要问我为什么晓得)。因为 executeCount
的值和 fn()
函数的调用次数呈正相干,所以这情理也就换成了咱们须要实现 batcher()
办法返回新的包装函数,该函数会被调用屡次,但最终只会执行一次 fn()
函数。
setTimeout 解法
因为题干中应用了 Promise.all()
,咱们自然而然想到应用异步去解决。也就是每次调用的时候会把所以的传参存下来,直到最初的时候再执行 fn()
返回对应的后果。问题在于什么时候触发开始执行呢?自然而然咱们想到了相似 debounce
的形式应用 setTimeout
减少延迟时间。
const batcher = f => {let nums = [];
const p = new Promise(resolve => setTimeout(_ => resolve(f(nums)), 100));
return arr => {
let start = nums.length;
nums = nums.concat(arr);
let end = nums.length;
return p.then(ret => ret.slice(start, end));
};
};
这里的难点在于事后定义了一个 Promise 在 100ms 之后才会 resolve。返回的函数实质只是将参数推入到 nums
数组中,待 100ms 后触发 resolve 返回对立执行 fn()
后的后果并获取对应于以后调用的后果片段。
起初有群友反馈,实际上不必定义 100ms 间接 0ms 也是能够的。因为 setTimeout
是在 UI 渲染完结之后才会执行的宏工作,所以实践上来说 setTimeout()
的最小距离值无奈设置为 0。它的最小值和浏览器的刷新频率有关系,依据 MDN 形容,它的最小值个别为 4ms。所以实践上它设置 0ms 和 100ms 成果是差不多的,都相似于 debounce
的成果。
Promise 解法
那么如何能实现提早 0ms 执行呢?咱们晓得除了宏工作之外 JS 还有微工作,微工作队列是在 JS 主线程执行实现之后立刻执行的事件队列。Promise 的回调就会存储在微工作队列中。于是咱们将 setTimeout
批改成了 Promise.resolve()
,最终发现也是能够实现同样的成果。
const batcher = f => {let nums = [];
const p = Promise.resolve().then(_ => f(nums));
return arr => {
let start = nums.length;
nums = nums.concat(arr);
let end = nums.length;
return p.then(ret => ret.slice(start, end));
};
};
因为 Promise 的微工作队列成果将 _ => f(nums)
推入微工作队列,待主线程的三次 batcherFn()
调用都执行实现之后才会执行。之后 p
的状态变为 fulfilled
后持续实现最终 slice
的操作。
后记
最终剖析下来,其实这情理的实质就是要通过某些办法将 fn()
函数的执行后置到主线程执行结束,至于是应用宏工作还是微工作队列,就看具体的需要了。除了 setTimeout()
之外,还有 setInterval()
, requestAnimationFrame()
都是宏工作队列。而微工作队列里除了有 Promise
之外,还有 MutationObserver
。对于宏工作和微工作队列相干的,感兴趣的能够看看《微工作、宏工作与 Event-Loop》这篇文章。