【每日一面(mian)】手寫(xie)防抖函數
基礎問答
問:手寫一個防抖函數
答:
/**
* 基礎版防抖函數(非立即執行)
* @param {Function} func - 需要防抖的目標函數
* @param {number} delay - 等待時間(毫秒)
* @returns {Function} - 防抖后的函數
*/
function debounce(func, delay) {
let timer = null; // 用閉包存儲定時器ID,確保多次調用共享同一個定時器
// 返回防抖后的函數,接收目標函數的參數(...args)
return function (...args) {
// 1. 若已有定時器,先清除(重復觸發時重新計時)
if (timer) clearTimeout(timer);
// 2. 重新設置定時器,等待delay后執行目標函數
timer = setTimeout(() => {
func.apply(this, args); // 用apply綁定this(確保目標函數this指向正確)
timer = null; // 執行后清空定時器,避免內存泄漏
}, delay);
};
}
擴展延伸
防抖(Debounce):
- 核心邏輯:當一個事件會被頻繁的觸發時,防抖函數不會頻繁的執行,而是等待事件停止觸發一段時間后才執行;如果在等待執行的過程中,事件再次被觸發,則我們需要重新計算需要等待的時間。
- 典型使用場景:搜索框輸入聯想(每次按鍵都會觸發輸入對應 Input 的事件,我們認為用戶結束輸入后,我們再進行聯想,這個結束輸入的判定規則就是,自上一次Input事件觸發后的一段時間內,用戶沒有再觸發Input事件,可視為結束輸入,需要開始聯想),實時輸入校驗等。
和(he)防抖并列(lie)提及(ji)的是節流(liu)。
節流(Throttle):
- 核心邏輯:當事件會被頻繁觸發的時候,節流函數只會按照固定的時間間隔執行,無論期間事件被處罰多少次,都只在每個時間的開頭(或結束)執行一次。
- 典型使用場景:滾動監聽(一般用于加載數據判斷,每隔一段時間判斷一次當前位置,來判斷需要加載的數據量),窗口resize事件(用于重新計算布局)
這里簡單(dan)對比一下(xia)防(fang)抖和節流:
| 防抖 | 節流 | |
|---|---|---|
| 執行時機 | 事件停止觸發后,等待一段指定的時間 | 固定時間間隔執行,每個時間段內僅執行一次 |
| 重復觸發的問題 | 重新計算等待的時間,執行會延遲 | 不影響,固定時間間隔執行 |
| 目標 | 解決冗余的執行 | 解決過度的執行 |
| 使用場景 | 搜索、校驗 | 滾動加載 |
搜索輸入的過程中,每次鍵入字符觸發搜索,在沒有防抖的情況下,僅僅只有最后一次的搜索(即用戶輸入完成)才是有效的,之前的這些,全部都是沒有意義的,只會加重服務負擔,即為 “冗余” 。
頁面滾動過程中,滾動是持續觸發的,在沒有節流的情況下,每一次滾動都會有大量的計算過程(假設你的滾動事件是有計算操作的),計算阻塞主線程,會導致頁面卡頓,無法正常滾動,即為 “過度” ,如果使(shi)用防(fang)抖(dou),滾動(dong)事件(jian)的持續觸(chu)發,會導致計算一直無法開始(shi),俗稱“不(bu)跟手(shou)”。
面試追問
-
setTimeout 的延時并不準,有沒有辦法實現一個更精確的時間檢測?
有,使用時間戳 +requestFrameAnimation實現。 -
頁面滾動加載數據一般用什么?搜索框輸入觸發聯想詞,又用什么?
滾動加載一般用節流,防抖需要等用戶停止滾動才加載,可能會等很久,節流則是一到底部就加載,可以保證加載的及時性。
搜索聯(lian)想(xiang)一般用(yong)防(fang)抖,因(yin)為用(yong)戶的(de)輸(shu)入(ru)過程(cheng)會頻繁觸(chu)發聯(lian)想(xiang),但(dan)是(shi)只有(you)用(yong)戶停(ting)止輸(shu)入(ru)時,觸(chu)發的(de)聯(lian)想(xiang)才是(shi)用(yong)戶想(xiang)要的(de)、有(you)效(xiao)的(de)。 -
我看你在防抖函數中,用了 apply 這是為啥?為啥不可以直接用 func ?
主要是(shi)(shi) this 指針的(de)(de)指向(xiang)問題(ti),防抖函(han)數返回的(de)(de)是(shi)(shi)一(yi)個新的(de)(de)函(han)數,假設現在設置的(de)(de)是(shi)(shi) input.oninput = debounceSearch,這(zhe)個 debounceSearch 中如果有 this,那么預期(qi)是(shi)(shi)要指向(xiang) input 標簽,但(dan)是(shi)(shi)我們(men)直接調用 func 的(de)(de)話,this 會指向(xiang) window 或 undefined,和預期(qi)不一(yi)致。 -
防抖函數中,如果目標函數有返回值,我們可以拿到嗎?
不行(xing)(xing)(xing),即(ji)使返回(hui)目標函數結(jie)果也(ye)不行(xing)(xing)(xing),因為他(ta)在(zai) setTimeout 里面執行(xing)(xing)(xing)的(de),無法返回(hui)對應的(de)執行(xing)(xing)(xing)結(jie)果。 -
但是我就需要這個返回結果,有沒有辦法?
有,兩(liang)種辦法,一是將(jiang) setTimout 用(yong)(yong) Promise 封裝起來,setTimeout 的(de)回調(diao)執行(xing)時,resolve 這個(ge)(ge) Promise 就可以了,這樣(yang)防抖函數就變成(cheng)了一個(ge)(ge)異步的(de)api,二是使用(yong)(yong)回調(diao)參數,在目標函數執行(xing)后,調(diao)用(yong)(yong)這個(ge)(ge)回調(diao)就可以了。 -
有沒有遇到過防抖函數導致內存泄漏的情況?
沒有,但(dan)是防抖函數有內存泄漏的(de)可能性,本質(zhi)上是閉包寫法產生的(de),編寫代碼的(de)時候注(zhu)意閉包的(de)處理就可以了。
本文首發于(yu),公眾號訂閱請關(guan)注:

