函數防抖和節流
函數防抖和函數節流:優化高頻率執行js代碼的一種手段,js中的一些事件如瀏覽器的resize、scroll,鼠標的mousemove、mouseover,input輸入框的keypress等事件在觸發時,會不斷地調用綁定在事件上的回調函數,極大地浪費資源,降低前端性能。為了優化體驗,需要對這類事件進行調用次數的限制。
函數防抖
在事件被觸發n秒后再執行回調,如果在這n秒內又被觸發,則重新計時。
根據函數防抖思路設計出第一版的最簡單的防抖代碼:
var timer; // 維護同一個timer function debounce(fn, delay) { clearTimeout(timer); timer = setTimeout(function(){ fn(); }, delay); }
用onmousemove測試一下防抖函數:
// test
function testDebounce() { console.log('test'); } document.onmousemove = () => { debounce(testDebounce, 1000); }
上面例子中的debounce就是防抖函數,在document中鼠標移動的時候,會在onmousemove最后觸發的1s后執行回調函數testDebounce;如果我們一直在瀏覽器中移動鼠標(比如10s),會發現會在10 + 1s后才會執行testDebounce函數(因為clearTimeout(timer)),這個就是函數防抖。
在上面的代碼中,會出現一個問題,var timer只能在setTimeout的父級作用域中,這樣才是同一個timer,並且為了方便防抖函數的調用和回調函數fn的傳參問題,我們應該用閉包來解決這些問題。
優化后的代碼:
function debounce(fn, delay) { var timer; // 維護一個 timer return function () { var _this = this; // 取debounce執行作用域的this var args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(function () { fn.apply(_this, args); // 用apply指向調用debounce的對象,相當於_this.fn(args); }, delay); }; }
測試用例:
// test
function testDebounce(e, content) { console.log(e, content); } var testDebounceFn = debounce(testDebounce, 1000); // 防抖函數 document.onmousemove = function (e) { testDebounceFn(e, 'debounce'); // 給防抖函數傳參 }
使用閉包后,解決傳參和封裝防抖函數的問題,這樣就可以在其他地方隨便將需要防抖的函數傳入debounce了。
函數節流
每隔一段時間,只執行一次函數。
- 定時器實現節流函數:
請仔細看清和防抖函數的代碼差異
function throttle(fn, delay) { var timer; return function () { var _this = this; var args = arguments; if (timer) { return; } timer = setTimeout(function () { fn.apply(_this, args); timer = null; // 在delay后執行完fn之后清空timer,此時timer為假,throttle觸發可以進入計時器 }, delay) } }
測試用例:
function testThrottle(e, content) { console.log(e, content); } var testThrottleFn = throttle(testThrottle, 1000); // 節流函數 document.onmousemove = function (e) { testThrottleFn(e, 'throttle'); // 給節流函數傳參 }
上面例子中,如果我們一直在瀏覽器中移動鼠標(比如10s),則在這10s內會每隔1s執行一次testThrottle,這就是函數節流。
函數節流的目的,是為了限制函數一段時間內只能執行一次。因此,定時器實現節流函數通過使用定時任務,延時方法執行。在延時的時間內,方法若被觸發,則直接退出方法。從而,實現函數一段時間內只執行一次。
根據函數節流的原理,我們也可以不依賴 setTimeout實現函數節流。
- 時間戳實現節流函數:
function throttle(fn, delay) { var previous = 0; // 使用閉包返回一個函數並且用到閉包函數外面的變量previous return function() { var _this = this; var args = arguments; var now = new Date(); if(now - previous > delay) { fn.apply(_this, args); previous = now; } } } // test function testThrottle(e, content) { console.log(e, content); } var testThrottleFn = throttle(testThrottle, 1000); // 節流函數 document.onmousemove = function (e) { testThrottleFn(e, 'throttle'); // 給節流函數傳參 }
其實現原理,通過比對上一次執行時間與本次執行時間的時間差與間隔時間的大小關系,來判斷是否執行函數。若時間差大於間隔時間,則立刻執行一次函數。並更新上一次執行時間。
異同比較
相同點:
- 都可以通過使用 setTimeout 實現。
- 目的都是,降低回調執行頻率。節省計算資源。
不同點:
- 函數防抖,在一段連續操作結束后,處理回調,利用clearTimeout 和 setTimeout實現。函數節流,在一段連續操作中,每一段時間只執行一次,頻率較高的事件中使用來提高性能。
- 函數防抖關注一定時間連續觸發的事件只在最后執行一次,而函數節流側重於一段時間內只執行一次。
常見應用場景
函數防抖的應用場景
連續的事件,只需觸發一次回調的場景有:
- 搜索框搜索輸入。只需用戶最后一次輸入完,再發送請求
- 手機號、郵箱驗證輸入檢測
- 窗口大小Resize。只需窗口調整完成后,計算窗口大小。防止重復渲染。
函數節流的應用場景
間隔一段時間執行一次回調的場景有:
- 滾動加載,加載更多或滾到底部監聽
- 谷歌搜索框,搜索聯想功能
- 高頻點擊提交,表單重復提交