JavaScript 函數(shù)防抖和函數(shù)節(jié)流.

JavaScript 函數(shù)防抖和函數(shù)節(jié)流.

在瀏覽器執(zhí)行環(huán)境中,等待主隊(duì)列任務(wù)(DOM TREE & CSS Tree & render Tree) 等任務(wù)執(zhí)行完畢之后.

就開(kāi)始執(zhí)行 EventLoop 環(huán)境事件了.

所以,某種程度上,瀏覽器是基于事件驅(qū)動(dòng)的

既然提到了事件,那么我們最常見(jiàn)的就是給一些元素綁定一些事件了.

比如給一個(gè)按鈕綁定一個(gè)click事件.


給元素綁定一個(gè)事件

給元素綁定一個(gè)事件,是一個(gè)非常常見(jiàn)的場(chǎng)景.

btn.onclick = function (e) {
    console.log('xxxxx')
}

這沒(méi)什么大不了的.


函數(shù)節(jié)流

先不要管什么是函數(shù)節(jié)流.

上述我們給一個(gè)按鈕添加了一個(gè)點(diǎn)擊事件.

每次點(diǎn)擊這個(gè)按鈕的時(shí)候,都會(huì)觸發(fā)這個(gè)事件響應(yīng)函數(shù).

這個(gè)是沒(méi)毛病的.

之前也都是這么做的.

但是如果,一個(gè)哥們單身30年,手速很快

假如這個(gè)哥們一秒能點(diǎn)擊100下. 且這又是一個(gè)發(fā)送ajax請(qǐng)求的按鈕.

那么,每一秒鐘

  • 前端瀏覽器,這個(gè)按鈕的事件響應(yīng)函數(shù)執(zhí)行了100次.發(fā)送了100個(gè)請(qǐng)求.
  • 后臺(tái)服務(wù)器每一秒接受到了100個(gè)請(qǐng)求.并處理這100個(gè)請(qǐng)求.

現(xiàn)在有一個(gè)需求是,這個(gè)按鈕一秒鐘不管點(diǎn)擊多少下,只能執(zhí)行一下.(你手速在快也沒(méi)有用)

這個(gè)需求描述的就是函數(shù)節(jié)流.

某些函數(shù)可能會(huì)出現(xiàn)非常頻繁的調(diào)用,但是在某一個(gè)周期內(nèi),不管觸發(fā)多少次,實(shí)際上只能被執(zhí)行一次.

所以需要這么一個(gè)機(jī)制:

  • 一個(gè)函數(shù)被執(zhí)行了.(此時(shí)計(jì)時(shí)是A)
  • 這個(gè)函數(shù)又被執(zhí)行了.(此時(shí)計(jì)時(shí)是B)
  • 如果(B-A<等待值),那么這個(gè)函數(shù)就不執(zhí)行.
  • 否則就執(zhí)行.
<h4>節(jié)流</h4>
<p>節(jié)流就是指函數(shù)大于等于某個(gè)時(shí)間周期才能執(zhí)行.否則就不執(zhí)行.一個(gè)周期范圍內(nèi)只能處理一次.</p>
<div id="show">0</div>
<button id='jl'>函數(shù)節(jié)流</button>
  let canClick = true
  let counter = 0
  document.getElementById('jl').addEventListener('click', function () {
    if (canClick) {
      canClick = false
      console.log(++counter)
      setTimeout(() => {
        canClick = true
      }, 1000);
    }
  }, false)

利用一個(gè) canClick 變量來(lái)標(biāo)記這個(gè)按鈕是否被點(diǎn)擊.

并在一個(gè) setTimeout 的定時(shí)器里,定時(shí)1秒去修改這個(gè) canClick

于是,目的就達(dá)到了..每次點(diǎn)擊之后,下一次點(diǎn)擊有效必須要等待一秒.

但這樣的做法,不具備通用性.

可以封裝一個(gè)函數(shù)節(jié)流的方法,把需要節(jié)流的函數(shù)通過(guò)這個(gè)方法封裝.

function throttle (handler, wait) {
    let lastTime = 0
    return function () {
        let nowTime = new Date().getTime()
        if (nowTime - lastTime > wait) {
            hanlder.apply(this,arguments)
            lastTime = nowTime
        }
    }
}
 function hanlder (e) {
    e.stopPropagation()
    show.innerText = parseInt(show.innerText) + 1
}

jl.addEventListener('click', throttle(hanlder, 1000), false)

函數(shù)節(jié)流:

  • 這個(gè)函數(shù)可能存在頻繁執(zhí)行的情況(手速飛快,點(diǎn)擊按鈕)
  • 我們希望這個(gè)函數(shù)在某個(gè)周期內(nèi)只執(zhí)行一次.

函數(shù)防抖

其實(shí)和函數(shù)節(jié)流一樣.

函數(shù)防抖也是用于解決某些函數(shù)頻繁觸發(fā)的情況.

但不同的是:

  • 函數(shù)節(jié)流,指的是在某一個(gè)周期內(nèi),只能執(zhí)行一次此函數(shù)
  • 函數(shù)防抖,指的是必須超過(guò)某一個(gè)時(shí)間閾值,否則函數(shù)不執(zhí)行.

一個(gè)比較常見(jiàn)的場(chǎng)景.

在文本框內(nèi)輸入搜索字符,并實(shí)時(shí)的發(fā)送搜索關(guān)鍵字到后臺(tái).

正常情況下,一般都是這么寫(xiě)的.

<input type="text" name="" id="inp">
 inp.addEventListener('input', function () {
    console.log(this.value)
  }, false)
image.png

發(fā)現(xiàn)每次輸入一個(gè)文本都會(huì)觸發(fā)一次input事件響應(yīng)函數(shù).

此函數(shù)的觸發(fā)頻率完全取決于用戶在輸入框中,輸入的文本快慢.

這沒(méi)有什么問(wèn)題,input 事件就是這么定義的.

但是對(duì)于實(shí)際場(chǎng)景而言,可能就出現(xiàn)了如下的不足:

  • 每次文本變動(dòng),都會(huì)觸發(fā) input 從而觸發(fā)后臺(tái)的請(qǐng)求操作.
  • 對(duì)于用于而言,可能需要查詢的是 123321 字符串. 而文本的輸入會(huì)導(dǎo)致之前的5次查詢都是無(wú)效的.

所以,這里就需要就一個(gè)機(jī)制..

  • 事件函數(shù)不是立馬執(zhí)行.
  • 會(huì)等待一段時(shí)間
  • 如果在等待的這一段事件內(nèi),事件函數(shù)又被觸發(fā)了.
  • 那么上一次的事件函數(shù)就不會(huì)執(zhí)行.
  • 接著等待一段時(shí)間.
  • 如此循環(huán)
  • 直到等待時(shí)間超過(guò)了,且用戶沒(méi)有操作了.我在執(zhí)行這個(gè)事件函數(shù).

這個(gè)就是所謂的函數(shù)節(jié)流.

除非函數(shù)不觸發(fā)了,且超過(guò)某個(gè)時(shí)間閾值,否則之前的事件都不會(huì)觸發(fā).

代碼改寫(xiě)為:

 let inp = document.getElementById('inp')
  let timer = null
  inp.addEventListener('input', function () {
    clearTimeout(timer)
    timer = setTimeout(() => {
      console.log(this.value)  
    }, 1000);
    
  }, false)

結(jié)果:

image.png

只有等待用戶操作停止了,并達(dá)到某一個(gè)等待的時(shí)間閾值,再最后觸發(fā)事件響應(yīng)函數(shù).

上述的代碼不具備通用性.

封裝函數(shù)節(jié)流方法 debounce

function debounce (hanlder, wait) {
    let timer = null
    return function () {
        clearTimeout(timer)
        timer = setTimeout(() => {
            hanlder.apply(this,arguments)
        }, wait)
    }
}

所以整體代碼可以改寫(xiě)為

 function inputHanlder(e) {
    console.log(this.value, e)
  }
  // 函數(shù)防抖 , 頻繁
  function debounce(hanlder, wait) {
    let timer = null
    return function () {
      clearTimeout(timer)
      timer = setTimeout(() => {
        hanlder.apply(this, arguments)
      }, wait);
    }
  }

  inp.addEventListener('input',debounce(inputHanlder,1000), false)

總結(jié)

  • 不管是函數(shù)防抖還是函數(shù)節(jié)流,都是為了解決函數(shù)頻繁執(zhí)行的問(wèn)題.
  • 函數(shù)防抖:函數(shù)在單位時(shí)間內(nèi),只會(huì)被觸發(fā)一次.
  • 函數(shù)節(jié)流:函數(shù)只有在超過(guò)了某個(gè)時(shí)間閾值后才會(huì)被執(zhí)行.否則函數(shù)不執(zhí)行.

一個(gè)只執(zhí)行一次(函數(shù)節(jié)流).

一個(gè)不滿足條件就一次也不執(zhí)行(函數(shù)防抖).

?著作權(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)容

  • 函數(shù)節(jié)流 還記得上篇文章中說(shuō)到的圖片懶加載嗎?我們?cè)谖恼碌淖詈髮?shí)現(xiàn)了一個(gè)頁(yè)面滾動(dòng)時(shí)按需加載圖片的方式,即在觸發(fā)滾動(dòng)...
    柏丘君閱讀 2,975評(píng)論 1 19
  • 在日常開(kāi)發(fā)中,我們經(jīng)常能夠碰到以下工作場(chǎng)景: 對(duì)提交按鈕進(jìn)行變態(tài)的點(diǎn)擊壓力測(cè)試輸入框內(nèi)容的實(shí)時(shí)校驗(yàn)(譬如驗(yàn)證用戶名...
    叫我小徐閱讀 1,127評(píng)論 0 5
  • 前言 最近和前端的小伙伴們,在討論面試題的時(shí)候。談到了函數(shù)防抖和函數(shù)節(jié)流的應(yīng)用場(chǎng)景和原理。于是,想深入研究一下兩者...
    youthcity閱讀 23,790評(píng)論 5 78
  • 我覺(jué)得我偶爾孩子氣很重,我想了想,這是好事,跟舒服的人相處才會(huì)有的樣子,我一直想把這種狀態(tài)拾回來(lái)
    茶花雨閱讀 189評(píng)論 0 0
  • 綜藝類: 王牌對(duì)王牌:我也是前幾天的時(shí)候看到的,室友推薦我去看的,真的是超搞笑,我現(xiàn)在在看第四季,每一期都很好,有...
    梁家_大少爺閱讀 244評(píng)論 0 1

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