写在前面:
本人没有这道题的标准答案,百度上也查不到相关的题解,以下思路都是本人个人想法,如有更好的答案,欢迎提出!
网易前端校招的一道面试题,对一个事件循环内所有调用进行防抖。
大家对防抖应该都有所耳闻,但什么是对一个事件循环内的调用进行防抖?本人也从未听说过,看第一遍的时候也没有想法。
但是这道题总结下来,应该是要实现两个需求:
- 多次同步调用
G
,只执行第一次 - 分别使用
setTimeout
异步调用G
和同步调用G
,各执行一次
既然是两个需求,那就一个一个来
先来实现第一个需求,这个比较简单,甚至也不用关心事件循环。跟 once
函数的实现比较类似,每次调用之前先判断一下是否已经调用,如已调用就不再调用了。
function debounce(func) {
let called = false;
return function() {
if(!called) {
called = true;
func();
}
}
}
虽然感觉哪里不对,但是第一个需求已经实现了。各位同学肯定也发现了,上面的代码存在一个问题,也就是同步调用 G
之后,没法通过 setTimeout
再次调用了,因为 called
已经变成了 true
。
接下来实现第二个需求。为了让 setTimeout
可以再次调用,我们需要通过一种手段,在所有同步代码执行完毕之后,将 called
再次改为 false
。
既然要在同步代码执行完毕之后修改 called
,我们能不能用 setTimeout
呢?按照这个思路,代码如下:
function debounce(func) {
let called = false;
return function() {
if(!called) {
called = true;
func();
setTimeout(() => {
called = false;
}, 0)
}
}
}
测试了一下发现,第一个需求没有问题,但是第二个需求还是只打印了一次。这是因为调用顺序的问题,在第二个需求下,setTimeout(G, 0)
先进入任务队列,然后执行到下一行 G()
的时候,防抖函数中的 setTimeout
才进入任务队列。大家都知道队列是先进先出,那么 setTimeout(G, 0)
会先于防抖函数中的 setTimeout
执行,当 setTimeout(G, 0)
执行的时候, called
还是 true
,所以还是只打印一次。
要解决这个问题,就要找到一种机制,它的执行时机在同步代码之后,在 setTimeout
等异步任务执行之前。我们知道,JS 中的异步任务其实分为两种,宏任务和微任务。
JS 在执行宏任务之前都会检查微任务队列,如果队列不为空,就先执行微任务,直到清空微任务队列,再执行宏任务
我们知道,setTimeout
、setInterval
、回调函数、文件I/O 等都属于宏任务,因此,为了实现题目中的需求,我们可以使用微任务修改 called
。常见的微任务包括 Promise
、MutationOserver
、process.nextTick
,我们使用最常见的 Promise
来实现。
function debounce(func) {
let called = false;
return function() {
if(!called) {
called = true;
func();
Promise.resolve().then(() => {
called = false;
})
}
}
}
经过测试,完美实现题目中的两个需求。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。