JS中的節流和去抖動

首先要明白 節流 Throttle 和 去抖動 Debounce 兩者是有區別的,很多人一開始都會搞混。
先講講去抖動 Debounce

Debounce

為什么要去抖動?

我們知道 瀏覽器有一些原生事件,比如 resize scroll keyup keydown 這些事件的回調函數,當他們觸發的時候,并不是想象中的只觸發一次,而是幾次甚至幾十次,如果當你的這些事件回調函數中有一些復雜的運算或者dom操作,低配瀏覽器很容易出現假死的狀態。

去抖動Debounce實現的效果是:以scroll來舉例,當scroll回調在指定的時間n毫秒內還會觸發,此次回調方法不執行,繼續等待n毫秒,直到n毫秒之后此方法不再觸發,執行這個方法。簡單來說就是:把在指定時間內可能會多次執行的方法打包成一次

window.debounce = function(fun,dely){  //fun 需要去抖動的方法,dely 指定的延遲時間
    var timer = null;  // 用閉包維護一個timer 做為定時器標識
    return function(){
        var context = this;  // 調用debounce的時候 保存執行上下文
        var args = arguments;  
        clearTimeout(timer);
        timer = setTimeout(function() {
            fun.apply(context , args);
         }, delay); // 設定定時器 判斷是否已經觸發 ,如果觸發則重新計時 等待dely毫秒再執行
    }
}

此時如果調用

foo = function(){
    console.log('scroll work')
}
dom.addEventListener('scroll', debounce(foo, 2000)); // 當dom連續觸發scroll 時 回調函數只會在兩秒后執行一次 

但是這種寫法有一個明顯的缺陷,就是當用戶觸發的第一時間方法是不會調用,所以上升級版

window.debounce = function(fun,dely){ 
    var timer = null; 
    return function(){
        var context = this;  
        var args = arguments;  
        if(timer) { clearTimeout(timer) }; // 看似多余的 但是是必須的 讀者可以自己思考為什么需要這么處理
        var doNow = !timer; // 判斷是否有定時器,如果有,就dely后清除timer,否則立即執行;
        timer = setTimeou(function(){
            timer = null ;
        },dely)
        if(doNow){
            fun.apply(context, args);
        }
    }
}

現在的效果是,你滾動的第一時間會觸發回調,然后你要是連續再觸發,在dely秒之內是不會觸發的,只有等dely毫秒后 timer 清除了,再觸發滾動才會調用回調。

想必兩個版本的問題大家都看出來了,多多少少都是有點奇怪。接下來就是節流登場了

Throttle

節流函數是處理類似場景但抖動不適合的另一種解決方案,比如大型電商網站當用戶滾動到頁面底部的時候再發AJAX請求獲取圖片,實現圖片懶加載,如果使用去抖動,不管方案一還是二,都會用種奇怪的體驗,假設設置500ms的delay時間,使用方案一,效果則是,用戶滾動了,500ms后發AJAX獲取圖片,再顯示圖片。期間500ms用戶是只能看到圖片缺失的。如果使用方案二,似乎是能實現需求,但是仔細想想,如果用戶不是500ms滾動一次,而是玩命的在連續滾動,則AJAX只會觸發一次,用戶只能看到第一次滾動觸發AJAX返回的圖片,后面的則是圖片缺失狀態。

到這應該可以猜到節流實現的什么效果了。

節流函數允許一個函數在規定的時間內只執行一次。

它和防抖動最大的區別就是,節流函數不管事件觸發有多頻繁,都會保證在規定時間內一定會執行一次真正的事件處理函數。

主要有兩種實現方法:

1.時間戳
2.定時器

時間戳實現:
window.throttle = function(fun,delay){
    var prev = Date.now(); // 閉包維護一個起始時間戳 
    return function(){
        var context = this;
        var args = arguments;
        var now = Date.now();  // 每次任務函數觸發的時候獲取時間戳
        if(now-prev>=delay){ // 判斷當前時間與起始時間戳的間隔 大余delay則觸發任務函數
            fun.apply(context,args);
            prev = Date.now(); // 關鍵是要更新閉包中的 起始時間戳
        }
    }
}

此時我們再測試

foo = function(){
    console.log('scroll work')
}
dom.addEventListener('scroll', throttle (foo, 1000)); // 當dom連續觸發scroll 時 任務函數每隔1秒也會觸發一次,當然眼尖朋友會發現有個小瑕疵
定時器實現:
var throttle = function(fun,delay){
    var timer = null; // 維護一個定時器
    return function(){
        var context = this;
        var args = arguments;
        if(!timer){ // 當任務函數觸發了 , 判斷定時器是否存在  不存在才執行任務函數
            timer = setTimeout(function(){ 
                fun.apply(context,args);
                timer = null;
            },delay);  // 當定時器不存在的時候 delay秒后才執行任務函數 并且清空定時器 接著下個輪回
        }
    }
}

當第一次觸發事件時,肯定不會立即執行函數,而是在delay秒后才執行。
之后連續不斷觸發事件,也會每delay秒執行一次。
當最后一次停止觸發后,由于定時器的delay延遲,可能還會執行一次函數。

可以綜合使用時間戳與定時器,完成一個事件觸發時立即執行,觸發完畢還能執行一次的節流函數:

window.throttle = function(fun,delay){
    var timer = null;
    var startTime = Date.now();  

    return function(){
        var curTime = Date.now();
        var remaining = delay-(curTime-startTime);  // 計算出兩次觸發的時間間隔有沒有大余delay 
        var context = this;
        var args = arguments;

        clearTimeout(timer);
        if(remaining<=0){ 
            func.apply(context,args);
            startTime = Date.now();  // 如果兩次觸發時間大余delay,則立馬觸發一次任務函數并且更新起始時間戳
        }else{
            timer = setTimeout(fun,remaining);  // 如果兩次觸發時間小于delay, 則改變定時器時間保證delay時間一定觸發任務函數
        }
    }
}

總結

防止一個事件頻繁觸發回調函數的方式:

  • 防抖動debounce:將幾次操作合并為一此操作進行。原理是維護一個計時器,規定在delay時間后觸發函數,但是在delay時間內再次觸發的話,就會取消之前的計時器而重新設置。這樣一來,只有最后一次操作能被觸發。

  • 節流throttle :使得一定時間內只觸發一次函數。
    它和防抖動最大的區別就是,節流函數不管事件觸發有多頻繁,都會保證在規定時間內一定會執行一次真正的事件處理函數,而防抖動只是在最后一次事件后才觸發一次函數。
    原理是通過判斷是否到達一定時間來觸發函數,若沒到規定時間則使用計時器延后,而下一次事件則會重新設定計時器。

---end

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容