再也不怕面試官問我防抖與節(jié)流了
最近去面試,又遇到面試官問我防抖與節(jié)流了,而明明前幾天就看過手寫代碼,卻寫不出來。有時候我在想,是不是自己太笨了。
回歸正題
防抖
先不說概念,按自己的理解,在單反里,有防抖機制。因為人在拿著單反的時候會手抖(單反重),按下快門的瞬間,照片會糊,所以有防抖機制,以防止新手把照片拍糊。
單反中的防抖是防止抖動,讓人拍出清晰的照片,JavaScript 中的防抖是為了什么?
同理,它的作用也是防止抖動。試想當你頻繁觸發(fā)一個事件時,就會引起不必要的性能損失,那么讓該事件在停止觸發(fā)后再觸發(fā),以此減少部分性能。
防抖的定義
防抖就是要延遲執(zhí)行,你一直操作觸發(fā)事件一直不執(zhí)行,當你停止操作等待多少秒后才執(zhí)行。
也就是說不管事件觸發(fā)頻率有多高,一定在事件觸發(fā) n 秒后執(zhí)行。如果在事件觸發(fā)的 n 秒又觸發(fā)了這個事件,那就以新事件的事件為準,n 秒后才執(zhí)行。總之,要等你觸發(fā)完事件 n 秒內(nèi)不再觸發(fā)事件,它才執(zhí)行。
手寫防抖
根據(jù)定義,我們知道要在時間 n 秒后執(zhí)行,那么我們就用定時器來實現(xiàn):
function debounce(event, wait) {
let timer = null;
return function (...args) {
clearTimeout(timer); // 清除setTimeout,使其回調(diào)函數(shù)不執(zhí)行
timer = setTimeout(() => {
event.apply(this, args)
}, wait)
}
}
代碼很簡單,即當還在觸發(fā)事件時,就清除 timer,使其在 n 秒后執(zhí)行,但此寫法首次不會立即執(zhí)行,為其健壯性,需加上判斷是否第一次執(zhí)行的第三個參數(shù) flag,判斷其是否立即執(zhí)行。
function debounce(event, wait, flag) {
let timer = null;
return function (...args) {
clearTimeout(timer)
if (!timer && flag) {
event.apply(this, args)
} else {
timer = setTimeout(() => {
event.apply(this, args)
}, wait)
}
}
}
防抖場景
窗口大小變化,調(diào)整樣式
window.addEventListener('resize', debounce(handleResize, 200))
搜索框,輸入后1000毫秒搜索
debounce(fetchSelectData, 300)
表單驗證,輸入 1000 毫秒后驗證
debounce(validator, 1000)
防抖帝王庫
兩大工具庫都有防抖源碼,可供參考:
- lodash-debounce。
- underscore-debounce。
節(jié)流
顧名思義,一節(jié)一節(jié)的流,就好似控制水閥,在事件不斷觸發(fā)的過程中,固定時間內(nèi)執(zhí)行一次事件。
手寫節(jié)流
因為是固定時間內(nèi)執(zhí)行一次時間,所以我們有兩種實現(xiàn)方法,一用時間戳,二用定時器。
時間戳
function throttle(event, wait) {
let pre = 0;
return function (...args){
if (new Date() - pre > wait) {
// 當 n 秒內(nèi)不重復(fù)執(zhí)行
pre = new Date();
event.apply(this, args)
}
}
}
使用時間戳雖然能實現(xiàn)節(jié)流,但是最后一次事件不會執(zhí)行。
定時器
function throttle(event, wait) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
event.apply(this, args)
}, wait)
}
}
}
使用定時器實現(xiàn)節(jié)流,雖然最后一次能觸發(fā),但是第一次不會觸發(fā)。
時間戳 + 定時器。
為解決第一次和最后一次都可以觸發(fā),把兩者結(jié)合起來。
function throttle(event, wait) {
let pre = 0, timer = null;
return function (...args) {
if (new Date() - pre > wait) {
clearTimeout(timer);
timer = null;
pre = new Date();
event.apply(this, args)
} else {
timer = setTimeout(() => {
event.apply(this, args)
}, wait)
}
}
}
節(jié)流場景
scroll 滾動
window.addEventListener('scroll', throttle(handleScroll, 200))
input 動態(tài)搜索
throttle(fetchInput, 300)
節(jié)流帝王庫
- lodash-throttle。
- underscore-throttle。
總結(jié)
防抖:只執(zhí)行最后一次。事件持續(xù)觸發(fā),但只有等事件停止觸發(fā)后 n 秒后才執(zhí)行函數(shù)。
節(jié)流:控制執(zhí)行頻率。持續(xù)觸發(fā),每 n 秒執(zhí)行一次函數(shù)。
對比圖: