手寫(xiě)實(shí)現(xiàn)節(jié)流防抖函數(shù),僅需十行代碼

什么是節(jié)流防抖?

在用戶瀏覽網(wǎng)頁(yè)或者在網(wǎng)頁(yè)上進(jìn)行一些操作時(shí),我們常常需要監(jiān)聽(tīng)一些事件去完成相應(yīng)的功能。比如鼠標(biāo)點(diǎn)擊、鍵盤(pán)輸入、滾輪滾動(dòng)等等一些事件。

在處理這些事件時(shí),我們可能會(huì)發(fā)現(xiàn),某些事件發(fā)生的頻率特別高,有的事件監(jiān)聽(tīng)函數(shù)甚至直接造成了頁(yè)面的卡頓和崩潰。

舉個(gè)例子,假如我們需要在用戶輸入時(shí),根據(jù)用戶的輸入內(nèi)容自動(dòng)聯(lián)想相關(guān)詞匯進(jìn)行搜索:

const input = document.querySelector('input')

input.addEventListener('input', () => {
  console.log(1)
})

此時(shí)可以看到這個(gè)效果:

每輸入一次就執(zhí)行一次

用戶每輸入一個(gè)字符就進(jìn)行一次相關(guān)操作,有時(shí)候很可能是還沒(méi)有輸入完成或者輸錯(cuò)的情況,但我們的代碼也進(jìn)行了一次操作,這就造成了一些不必要的網(wǎng)絡(luò)請(qǐng)求和性能浪費(fèi)。

所以,我們并不需要代碼如此高頻率的執(zhí)行,這在一定程度上對(duì)系統(tǒng)資源造成了浪費(fèi),程序的性能也會(huì)因此變得很差。因此,我們需要對(duì)這些事件的監(jiān)聽(tīng)做更進(jìn)一步的處理,對(duì)代碼的執(zhí)行頻率進(jìn)行一些限制。這就是所謂的節(jié)流防抖

節(jié)流防抖的原理

節(jié)流和防抖,二者的作用雖然相同,但是在原理和實(shí)現(xiàn)上存在一定的區(qū)別:

節(jié)流:頻繁地觸發(fā)某個(gè)事件,但一定的時(shí)間內(nèi)只執(zhí)行一次。函數(shù)名通常為 throttle。

防抖:頻繁地觸發(fā)某個(gè)事件,但只執(zhí)行最后一次。函數(shù)名通常為 debounce

實(shí)現(xiàn)節(jié)流函數(shù)

雖然事件不斷地被觸發(fā),但節(jié)流函數(shù)就就像紅綠燈一樣,不管前面有多少車(chē)輛排隊(duì),我就是需要等待一段時(shí)間才變?yōu)榫G燈,你才能通行。

所以最直接的方式還是利用定時(shí)器去處理。當(dāng)事件首次被觸發(fā)時(shí),開(kāi)始計(jì)時(shí) wait 毫秒,wait 毫秒以內(nèi)不管觸發(fā)了多少次,都會(huì)被忽略,直到計(jì)時(shí)結(jié)束后再去執(zhí)行。

思路有了,方法就不難實(shí)現(xiàn)。將事件處理函數(shù)設(shè)為第一個(gè)參數(shù) func,將等待時(shí)間設(shè)為第二個(gè)參數(shù) wait。

// 節(jié)流函數(shù)
function throttle(func, wait) {
  // 創(chuàng)建一個(gè)定時(shí)器
  let timeout = null
  // 返回一個(gè)函數(shù),這個(gè)函數(shù)會(huì)在一個(gè)時(shí)間區(qū)間結(jié)束后調(diào)用 func
  return function() {
    // 如果定時(shí)器還在運(yùn)行,則跳過(guò)
    if (timeout) return
    // 否則,設(shè)置定時(shí)器,等待 wait 毫秒后執(zhí)行 func 函數(shù)
    timeout = setTimeout(() => {
      // 執(zhí)行 func 函數(shù)
      func.apply(this, arguments)
      // 清空定時(shí)器
      timeout = null
    }, wait)
  }
}

在上面的代碼中,我們創(chuàng)建了一個(gè)定時(shí)器,并將它賦值給了一個(gè)變量 timeout。當(dāng)事件被觸發(fā)時(shí),我們檢查定時(shí)器是否在運(yùn)行,如果在運(yùn)行,則跳過(guò),否則執(zhí)行。

為 input 事件添加節(jié)流函數(shù):

input.addEventListener('input', throttle(handleInput, 500))

運(yùn)行效果:

使用了節(jié)流函數(shù),每過(guò)一段時(shí)間執(zhí)行一次

實(shí)現(xiàn)防抖函數(shù)

如何判斷是否為頻繁觸發(fā)的最后一次,很簡(jiǎn)單,我們只需要在第一次觸發(fā)該事件時(shí),設(shè)定一個(gè)等待 wait 毫秒的定時(shí)器,在計(jì)時(shí)器結(jié)束之前如果再次被觸發(fā),則重新計(jì)時(shí),直至不再被觸發(fā),再去執(zhí)行傳入的函數(shù)。

根據(jù)這個(gè)思路,我們就可以很輕松的實(shí)現(xiàn)一個(gè)基礎(chǔ)的 debounce 方法。將事件處理函數(shù)設(shè)為第一個(gè)參數(shù) func,將等待時(shí)間設(shè)為第二個(gè)參數(shù) wait。

// 防抖函數(shù)
function debounce(func, wait) {
  // 創(chuàng)建一個(gè)定時(shí)器
  let timeout
  // 返回一個(gè)函數(shù),這個(gè)函數(shù)會(huì)在一個(gè)時(shí)間區(qū)間結(jié)束后調(diào)用 func
  return function() {
    // 如果定時(shí)器還在運(yùn)行,則清除定時(shí)器
    timeout && clearTimeout(timeout)
    // 否則,設(shè)定定時(shí)器,等待 wait 毫秒后執(zhí)行 func 函數(shù)
    timeout = setTimeout(() => {
      // 執(zhí)行 func 函數(shù)
      func.apply(this, arguments)
    }, wait)
  }
}

在上面的代碼中,我們創(chuàng)建了一個(gè)定時(shí)器,并將它賦值給了一個(gè)變量 timeout。當(dāng)事件被觸發(fā)時(shí),我們檢查定時(shí)器是否在運(yùn)行,如果在運(yùn)行,則清除定時(shí)器,否則執(zhí)行。

為 input 事件添加防抖函數(shù):

input.addEventListener('input', debounce(handleInput, 500))

運(yùn)行效果:

使用了防抖函數(shù),停止輸入后再去執(zhí)行

apply

上面的代碼中使用了 apply,它是 Function.prototype 的一個(gè)方法,作用是將一個(gè)函數(shù)的作用域設(shè)置為某個(gè)對(duì)象,并將參數(shù)傳入函數(shù)。

有關(guān) apply 的詳細(xì)介紹,可以點(diǎn)擊 這里 查看。

源碼下載

點(diǎn)擊 這里 查看源碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容