高頻:手寫一個(gè)防抖函數(shù) Debounce
本文轉(zhuǎn)載自微信公眾號(hào)「三分鐘學(xué)前端」,作者sisterAn 。轉(zhuǎn)載本文請(qǐng)聯(lián)系三分鐘學(xué)前端公眾號(hào)。
我們上次 手寫了一個(gè)節(jié)流函數(shù)throttle ,這周我們繼續(xù)手寫手寫一個(gè)防抖函數(shù) debounce
手寫一個(gè) debounce
防抖函數(shù) debounce 指的是某個(gè)函數(shù)在某段時(shí)間內(nèi),無論觸發(fā)了多少次回調(diào),都只執(zhí)行最后一次。
實(shí)現(xiàn)原理就是利用定時(shí)器,函數(shù)第一次執(zhí)行時(shí)設(shè)定一個(gè)定時(shí)器,之后調(diào)用時(shí)發(fā)現(xiàn)已經(jīng)設(shè)定過定時(shí)器就清空之前的定時(shí)器,并重新設(shè)定一個(gè)新的定時(shí)器,如果存在沒有被清空的定時(shí)器,當(dāng)定時(shí)器計(jì)時(shí)結(jié)束后觸發(fā)函數(shù)執(zhí)行。
- // fn 是需要防抖處理的函數(shù)
- // wait 是時(shí)間間隔
- function debounce(fn, wait = 50) {
- // 通過閉包緩存一個(gè)定時(shí)器 id
- let timer = null
- // 將 debounce 處理結(jié)果當(dāng)作函數(shù)返回
- // 觸發(fā)事件回調(diào)時(shí)執(zhí)行這個(gè)返回函數(shù)
- return function(...args) {
- // this保存給context
- const context = this
- // 如果已經(jīng)設(shè)定過定時(shí)器就清空上一次的定時(shí)器
- if (timer) clearTimeout(timer)
- // 開始設(shè)定一個(gè)新的定時(shí)器,定時(shí)器結(jié)束后執(zhí)行傳入的函數(shù) fn
- timer = setTimeout(() => {
- fn.apply(context, args)
- }, wait)
- }
- }
- // DEMO
- // 執(zhí)行 debounce 函數(shù)返回新函數(shù)
- const betterFn = debounce(() => console.log('fn 防抖執(zhí)行了'), 1000)
- // 停止滑動(dòng) 1 秒后執(zhí)行函數(shù) () => console.log('fn 防抖執(zhí)行了')
- document.addEventListener('scroll', betterFn)
不過 underscore 中的 debounce 還有第三個(gè)參數(shù):immediate 。這個(gè)參數(shù)是做什么用的呢?
傳參 immediate 為 true, debounce會(huì)在 wait 時(shí)間間隔的開始調(diào)用這個(gè)函數(shù) 。(注:并且在 wait 的時(shí)間之內(nèi),不會(huì)再次調(diào)用。)在類似不小心點(diǎn)了提交按鈕兩下而提交了兩次的情況下很有用。
把 true 傳遞給 immediate 參數(shù),會(huì)讓 debounce 在 wait 時(shí)間開始計(jì)算之前就觸發(fā)函數(shù)(也就是沒有任何延時(shí)就觸發(fā)函數(shù)),而不是過了 wait 時(shí)間才觸發(fā)函數(shù),而且在 wait 時(shí)間內(nèi)也不會(huì)觸發(fā)(相當(dāng)于把 fn 的執(zhí)行鎖住)。如果不小心點(diǎn)了兩次提交按鈕,第二次提交就會(huì)不會(huì)執(zhí)行。
那我們根據(jù) immediate 的值來決定如何執(zhí)行 fn 。如果是 immediate 的情況下,我們立即執(zhí)行 fn ,并在 wait 時(shí)間內(nèi)鎖住 fn 的執(zhí)行, wait 時(shí)間之后再觸發(fā),才會(huì)重新執(zhí)行 fn ,以此類推。
- // immediate 表示第一次是否立即執(zhí)行
- function debounce(fn, wait = 50, immediate) {
- let timer = null
- return function(...args) {
- // this保存給context
- const context = this
- if (timer) clearTimeout(timer)
- // immediate 為 true 表示第一次觸發(fā)后執(zhí)行
- // timer 為空表示首次觸發(fā)
- if (immediate && !timer) {
- fn.apply(context, args)
- }
- timer = setTimeout(() => {
- fn.apply(context, args)
- }, wait)
- }
- }
- // DEMO
- // 執(zhí)行 debounce 函數(shù)返回新函數(shù)
- const betterFn = debounce(() => console.log('fn 防抖執(zhí)行了'), 1000, true)
- // 第一次觸發(fā) scroll 執(zhí)行一次 fn,后續(xù)只有在停止滑動(dòng) 1 秒后才執(zhí)行函數(shù) fn
- document.addEventListener('scroll', betterFn)
underscore 源碼解析
看完了上文的基本版代碼,感覺還是比較輕松的,現(xiàn)在來學(xué)習(xí)下 underscore 是如何實(shí)現(xiàn) debounce 函數(shù)的,學(xué)習(xí)一下優(yōu)秀的思想,直接上代碼和注釋,本源碼解析依賴于 underscore 1.9.1 版本實(shí)現(xiàn)。
- // 此處的三個(gè)參數(shù)上文都有解釋
- _.debounce = function(func, wait, immediate) {
- // timeout 表示定時(shí)器
- // result 表示 func 執(zhí)行返回值
- var timeout, result;
- // 定時(shí)器計(jì)時(shí)結(jié)束后
- // 1、清空計(jì)時(shí)器,使之不影響下次連續(xù)事件的觸發(fā)
- // 2、觸發(fā)執(zhí)行 func
- var later = function(context, args) {
- timeout = null;
- // if (args) 判斷是為了過濾立即觸發(fā)的
- // 關(guān)聯(lián)在于 _.delay 和 restArguments
- if (args) result = func.apply(context, args);
- };
- // 將 debounce 處理結(jié)果當(dāng)作函數(shù)返回
- var debounced = restArguments(function(args) {
- if (timeout) clearTimeout(timeout);
- if (immediate) {
- // 第一次觸發(fā)后會(huì)設(shè)置 timeout,
- // 根據(jù) timeout 是否為空可以判斷是否是首次觸發(fā)
- var callNow = !timeout;
- timeout = setTimeout(later, wait);
- if (callNow) result = func.apply(this, args);
- } else {
- // 設(shè)置定時(shí)器
- timeout = _.delay(later, wait, this, args);
- }
- return result;
- });
- // 新增 手動(dòng)取消
- debounced.cancel = function() {
- clearTimeout(timeout);
- timeout = null;
- };
- return debounced;
- };
- // 根據(jù)給定的毫秒 wait 延遲執(zhí)行函數(shù) func
- _.delay = restArguments(function(func, wait, args) {
- return setTimeout(function() {
- return func.apply(null, args);
- }, wait);
- });
相比上文的基本版實(shí)現(xiàn),underscore 多了以下幾點(diǎn)功能。
1、函數(shù) func 的執(zhí)行結(jié)束后返回結(jié)果值 result
2、定時(shí)器計(jì)時(shí)結(jié)束后清除 timeout ,使之不影響下次連續(xù)事件的觸發(fā)
3、新增了手動(dòng)取消功能 cancel
4、immediate 為 true 后只會(huì)在第一次觸發(fā)時(shí)執(zhí)行,頻繁觸發(fā)回調(diào)結(jié)束后不會(huì)再執(zhí)行
參考
深入淺出防抖函數(shù) debounce
前端面試題——自己實(shí)現(xiàn)debounce