js防抖節(jié)流

防抖和節(jié)流嚴格算起來應該屬于性能優(yōu)化的知識,但實際上遇到的頻率相當高,處理不當或者放任不管就容易引起瀏覽器卡死。所以還是很有必要早點掌握的致燥。(信我,你看完肯定就懂了)

從滾動條監(jiān)聽的例子說起

先說一個常見的功能怖侦,很多網站會提供這么一個按鈕:用于返回頂部篡悟。



這個按鈕只會在滾動到距離頂部一定位置之后才出現(xiàn),那么我們現(xiàn)在抽象出這個功能需求-- 監(jiān)聽瀏覽器滾動事件匾寝,返回當前滾條與頂部的距離
這個需求很簡單搬葬,直接寫:

function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滾動條位置:' + scrollTop);
}
window.onscroll  = showTop

但是!在運行的時候會發(fā)現(xiàn)存在一個問題:這個函數(shù)的默認執(zhí)行頻率艳悔,太急凰!高!了猜年!抡锈。 高到什么程度呢?以chrome為例乔外,我們可以點擊選中一個頁面的滾動條床三,然后點擊一次鍵盤的【向下方向鍵】,會發(fā)現(xiàn)函數(shù)執(zhí)行了8-9次杨幼!然而實際上我們并不需要如此高頻的反饋撇簿,畢竟瀏覽器的性能是有限的,不應該浪費在這里差购,所以接著討論如何優(yōu)化這種場景四瘫。

防抖(debounce)觸發(fā)高頻事件后 n 秒內函數(shù)只會執(zhí)行一次,如果 n 秒內高頻事件再次被觸發(fā)欲逃,則重新計算時間

基于上述場景找蜜,首先提出第一種思路:在第一次觸發(fā)事件時,不立即執(zhí)行函數(shù)稳析,而是給出一個期限值比如200ms洗做,然后:
如果在200ms內沒有再次觸發(fā)滾動事件,那么就執(zhí)行函數(shù)
如果在200ms內再次觸發(fā)滾動事件彰居,那么當前的計時取消竭望,重新開始計時
效果:如果短時間內大量觸發(fā)同一事件,只會在操作結束后一定時間內執(zhí)行一次函數(shù)裕菠。

實現(xiàn):既然前面都提到了計時,那實現(xiàn)的關鍵就在于setTimeout這個函數(shù)闭专,由于還需要一個變量來保存計時奴潘,考慮維護全局純凈旧烧,可以借助閉包來實現(xiàn):

/*
* fn [function] 需要防抖的函數(shù)
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null //借助閉包
    return function() {
        if(timer){
            clearTimeout(timer) //進入該分支語句画髓,說明當前正在一個計時過程中掘剪,并且又觸發(fā)了相同事件。所以要取消當前的計時奈虾,重新開始計時
            timer = setTimeout(()=> {
                fn()
            },delay) 
        }else{
            timer = setTimeout(()=> {
                fn()
            },delay)  // 進入該分支說明當前并沒有在計時夺谁,那么就開始一個計時
        }
    }
}

當然 上述代碼是為了貼合思路,方便理解肉微,寫完會發(fā)現(xiàn)其實 time = setTimeout(fn,delay)是一定會執(zhí)行的匾鸥,所以可以稍微簡化下:


/*****************************簡化后的分割線 ******************************/
function debounce(fn,delay){
    let timer = null //借助閉包
    return function() {
        if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(()=> {
            fn()
        },delay) // 簡化寫法
    }
}
// 然后是舊代碼
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滾動條位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 為了方便觀察效果我們取個大點的間斷值,實際使用根據需要來配置

此時會發(fā)現(xiàn)碉纳,必須在停止?jié)L動1秒以后勿负,才會打印出滾動條位置。

到這里劳曹,已經把防抖實現(xiàn)了

實現(xiàn)方式:每次觸發(fā)事件時設置一個延遲調用方法奴愉,并且取消之前的延時調用方法
缺點:如果事件在規(guī)定的時間間隔內被不斷的觸發(fā),則調用方法會被不斷的延遲


節(jié)流(throttle)高頻事件觸發(fā)铁孵,但在 n 秒內只會執(zhí)行一次锭硼,所以節(jié)流會稀釋函數(shù)的執(zhí)行頻率

繼續(xù)思考,使用上面的防抖方案來處理問題的結果是:

如果在限定時間段內蜕劝,不斷觸發(fā)滾動事件(比如某個用戶閑著無聊檀头,按住滾動不斷的拖來拖去),只要不停止觸發(fā)熙宇,理論上就永遠不會輸出當前距離頂部的距離鳖擒。
但是如果產品同學的期望處理方案是:即使用戶不斷拖動滾動條,也能在某個時間間隔之后給出反饋呢烫止?(此處暫且不論哪種方案更合適蒋荚,既然產品爸爸說話了我們就先考慮怎么實現(xiàn))
其實很簡單:我們可以設計一種類似控制閥門一樣定期開放的函數(shù),也就是讓函數(shù)執(zhí)行一次后馆蠕,在某個時間段內暫時失效期升,過了這段時間后再重新激活(類似于技能冷卻時間)。

效果:如果短時間內大量觸發(fā)同一事件互躬,那么在函數(shù)執(zhí)行一次之后播赁,該函數(shù)在指定的時間期限內不再工作,直至過了這段時間才重新生效吼渡。

實現(xiàn) 這里借助setTimeout來做一個簡單的實現(xiàn)容为,加上一個狀態(tài)位valid來表示當前函數(shù)是否處于工作狀態(tài):

function throttle(fn,delay){
    let valid = true
    return function() {
       if(!valid){
           //休息時間 暫不接客
           return false 
       }
       // 工作時間,執(zhí)行函數(shù)并且在間隔期內把狀態(tài)位設為無效
        fn()
        valid = false
        setTimeout(() => {
            valid = true;
        }, delay)
    }
}
/* 請注意,節(jié)流函數(shù)并不止上面這種實現(xiàn)方案,
   例如可以完全不借助setTimeout坎背,可以把狀態(tài)位換成時間戳替劈,然后利用時間戳差值是否大于指定間隔時間來做判定。
   也可以直接將setTimeout的返回的標記當做判斷條件-判斷當前定時器是否存在得滤,如果存在表示還在冷卻陨献,并且在執(zhí)行fn之后消除定時器表示激活,原理都一樣
    */

// 以下照舊
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滾動條位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000) 

運行以上代碼的結果是:

如果一直拖著滾動條進行滾動懂更,那么會以1s的時間間隔眨业,持續(xù)輸出當前位置和頂部的距離

實現(xiàn)方式:每次觸發(fā)事件時,如果當前有等待執(zhí)行的延時函數(shù)沮协,則直接return


區(qū)別:防抖動是將多次執(zhí)行變?yōu)樽詈笠淮螆?zhí)行龄捡,節(jié)流是將多次執(zhí)行變成每隔一段時間執(zhí)行。

其他應用場景舉例

講完了這兩個技巧皂股,下面介紹一下平時開發(fā)中常遇到的場景:

1.搜索框input事件墅茉,例如要支持輸入實時搜索可以使用節(jié)流方案(間隔一段時間就必須查詢相關內容),或者實現(xiàn)輸入間隔大于某個值(如500ms)呜呐,就當做用戶輸入完成就斤,然后開始搜索,具體使用哪種方案要看業(yè)務需求蘑辑。
2.頁面resize事件洋机,常見于需要做頁面適配的時候。需要根據最終呈現(xiàn)的頁面情況進行dom渲染(這種情形一般是使用防抖洋魂,因為只需要判斷最后一次的變化情況)
參考來源:https://segmentfault.com/a/1190000018428170

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末绷旗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子副砍,更是在濱河造成了極大的恐慌衔肢,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豁翎,死亡現(xiàn)場離奇詭異角骤,居然都是意外死亡,警方通過查閱死者的電腦和手機心剥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門邦尊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人优烧,你說我怎么就攤上這事蝉揍。” “怎么了畦娄?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵又沾,是天一觀的道長弊仪。 經常有香客問我,道長捍掺,這世上最難降的妖魔是什么撼短? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮挺勿,結果婚禮上,老公的妹妹穿的比我還像新娘喂柒。我一直安慰自己不瓶,他們只是感情好,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布灾杰。 她就那樣靜靜地躺著蚊丐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艳吠。 梳的紋絲不亂的頭發(fā)上麦备,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音昭娩,去河邊找鬼凛篙。 笑死,一個胖子當著我的面吹牛栏渺,可吹牛的內容都是我干的呛梆。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼磕诊,長吁一口氣:“原來是場噩夢啊……” “哼填物!你這毒婦竟也來了?” 一聲冷哼從身側響起霎终,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤滞磺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后莱褒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體击困,經...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年保礼,在試婚紗的時候發(fā)現(xiàn)自己被綠了沛励。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡炮障,死狀恐怖目派,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情胁赢,我是刑警寧澤企蹭,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響谅摄,放射性物質發(fā)生泄漏徒河。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一送漠、第九天 我趴在偏房一處隱蔽的房頂上張望顽照。 院中可真熱鬧,春花似錦闽寡、人聲如沸代兵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽植影。三九已至,卻和暖如春涎永,著一層夾襖步出監(jiān)牢的瞬間思币,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工羡微, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谷饿,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓拷淘,卻偏偏與公主長得像各墨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子启涯,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內容

  • 防抖(debounce) 思路:在第一次觸發(fā)事件時贬堵,不立即執(zhí)行函數(shù),而是給出一個期限值比如200ms结洼,然后: 如果...
    alanwhy閱讀 1,414評論 0 21
  • 防抖和節(jié)流嚴格算起來應該屬于性能優(yōu)化的知識黎做,但實際上遇到的頻率相當高,處理不當或者放任不管就容易引起瀏覽器卡死松忍。 ...
    Sun____閱讀 271評論 0 2
  • 使用window.addEventListener()進行窗口的resize蒸殿、scroll、輸入框實時監(jiān)控等操作時...
    DDLH閱讀 389評論 0 1
  • JS防抖和節(jié)流 在進行窗口的resize鸣峭、scroll宏所,輸入框內容校驗等操作時,如果事件處理函數(shù)調用的頻率無限制...
    Never_Settile閱讀 540評論 0 0
  • 防抖(JS的事件多次觸發(fā)摊溶,只執(zhí)行最后一次) 應用場景: input輸入信息進行搜索爬骤,如果每敲一個字符就請求后臺接口...
    一名有馬甲線的程序媛閱讀 646評論 0 2