前端百題斬—通俗易懂的防抖與節流
性能一直是前端老生常談的一個話題,其中有一個性能問題就是我們會頻繁的觸發一些事件,例如mousemove、scroll、resize等,雖然瀏覽器已經對這些事件的觸發做了一些優化,但是如果在很短的時間內頻繁的觸發仍然會影響性能,這個時候就需要今天的主角:防抖和節流,利用它們來進行優化,提高性能。
1 防抖
1.1 定義
防抖就是將多次高頻操作優化為只在最后一次執行(某個函數在某段時間內,無論觸發了多少次回調,都只執行最后一次)。通常的使用場景是:用戶輸入,只需在輸入完成后做一次輸入校驗即可。
1.2 實現
防抖是將多次操作合并為一次操作完成,其原理就是維護一個計時器,在規定的時間后觸發函數,但是在該規定時間內再次觸發的話就會取消之前的定時器而重新設置,從而保證了只有最后一次操作能夠被觸發。其實現步驟如下所示:
- 利用閉包保存一個timer變量,然后返回一個函數(這個返回的函數就是后續頻繁觸發操作中調用的函數);
- 根據標志位判斷是否第一次需要立即執行(因為有些情況是需要首次調用函數立即執行的,若沒有該參數,就會在定時器到了之后才會執行);
- 當有新的觸發時,若存在定時器,則清空該定時器;
- 設定一個新的定時器,重新計時。
- function debounce(fn, wait, immediate) {
- let timer = null;
- return function (...args) {
- // 立即執行的功能(timer為空表示首次觸發)
- if (immediate && !timer) {
- fn.apply(this, args);
- }
- // 有新的觸發,則把定時器清空
- timer && clearTimeout(timer);
- // 重新計時
- timer = setTimeout(() => {
- fn.apply(this, args);
- }, wait);
- }
- }
1.3 效果預覽

觀察效果圖可以驗證上述的理論知識:
- 防抖之后輸出內容的頻次降低了;
- 防抖之后,其在超過一定時間之后才會輸出內容。
2 節流
2.1 定義
節流就是每隔一段時間后執行一次,也就是降低頻率,將高頻操作優化成低頻操作。通常使用場景:滾動條事件、resize事件、動畫等,通常每隔100-500ms執行一次即可。
2.2 實現
節流函數的實現方式有兩種:定時器版本、時間戳版本,這兩者各有千秋,下面來簡要實現一下。
2.2.1 定時器版本
定時器版本的節流函數其重點是利用閉包保存timer變量,具有兩個特點:
- n秒后才會執行第一次(定時器到了時間后才會觸發);
- 停止觸發后節流函數還會執行一次(因為該函數是延遲執行的,當停止觸發時其任務已經到了隊列中,所以停止后還會執行一次)。
- // 定時器版本
- function throttle(fn, wait) {
- let timer = null;
- return function(...args) {
- if (!timer) {
- timer = setTimeout(() => {
- fn.apply(this, args);
- timer = null;
- }, wait)
- }
- }
- }
2.2.2 時間戳版本
時間戳版本的節流函數重點是利用閉包保存上一次的時間previous,具有兩個特點:
開始觸發后會立即執行(因為previous開始會被賦值為0);
停止觸發后不再執行(因為該函數是同步任務,在觸發的時候就會進行相應的判斷,所以就不存在停止觸發后再執行的情況)。
- // 時間戳版本
- function throttle(fn, wait) {
- // 上一次執行時間
- let previous = 0;
- return function(...args) {
- // 當前時間
- let now = +new Date();
- if (now - previous > wait) {
- previous = now;
- fn.apply(this, args);
- }
- }
- }
2.3 效果預覽
觀察效果圖可以驗證上述的理論知識:
- 節流確實降低了內容的輸出頻率,將高頻變為低頻;
- 時間戳版本的節流函數在首次會輸出內容,但是最后一次的內容不會輸出(謹慎使用);
- 定時器版本的節流函數確實不會立刻打印內容,而是超過一定時間之后才會打印;此外,其最后輸入的內容會被打印出來。