一步一步認(rèn)識debounce和throttle(防抖和節(jié)流)

其實早就聽說了節(jié)流和防抖的解決方案。這兩個的目的都是為了提升頁面中監(jiān)聽事件的性能吮蛹。才疏學(xué)淺轿偎,如果有錯誤广凸,也希望各位指正。

一 為什么會出現(xiàn)debounce和throttle

在2011年的時候蛛枚,有一篇報告這樣說:當(dāng)你向下滾動twitter頁面的時候谅海,越向下滾動就會發(fā)現(xiàn)頁面會越來越卡頓甚至無響應(yīng)。John Resig 就在一篇博客中提到蹦浦,在一些高觸發(fā)的事件如scroll,resize事件中扭吁,直接在監(jiān)聽事件內(nèi)部做出響應(yīng)是非常不好的舉措。 https://johnresig.com/blog/learning-from-twitter/

相信盲镶,你也寫過這類dom事件的監(jiān)聽操作侥袜,也應(yīng)該清楚地知道,當(dāng)你監(jiān)聽scroll,resize事件的時候溉贿,事件觸發(fā)的頻率是非常高的系馆,(具體的時間我沒有考量)。如果你監(jiān)聽事件直接每次都進(jìn)行相關(guān)的dom操作顽照,消耗是非常大的由蘑。看一個pen上的例子代兵。
下面這個是監(jiān)聽scroll事件的操作尼酿,在監(jiān)聽滾動事件的時候,數(shù)字自動累計增加植影。
可以看到裳擎,在每一次滾動的時候,事件觸發(fā)的頻率是非常高的思币。
所以鹿响,
就有了節(jié)流和防抖機制的出現(xiàn),其實這兩個也沒有什么特殊的谷饿,只不過是定時器的應(yīng)用而已惶我。

test.gif

二 debounce 和 throttle是什么 ?

debounce,防抖博投,其實就是通過設(shè)置定時器绸贡,讓高頻觸發(fā)的事件在觸發(fā)結(jié)束之后再做出相關(guān)響應(yīng)。只執(zhí)行一次毅哗。
throttle听怕,節(jié)流。其實也是通過設(shè)置定時器虑绵,讓高頻連續(xù)觸發(fā)的事件每隔一定的時間長度之后再做出響應(yīng)尿瞭。是有規(guī)律的時間間隔去執(zhí)行。

什么意思呢翅睛? 看一下上面的gif圖声搁,原本是一直在觸發(fā)的黑竞,那么我們可以通過設(shè)置debounce防抖,達(dá)到當(dāng)滾動停止之后酥艳,再觸發(fā)事件摊溶。
那么?是否也可以通過設(shè)置throttle節(jié)流來實現(xiàn)充石?throttle其實就是不要在事件結(jié)束之后再響應(yīng)莫换,而是在這個時間段內(nèi)持續(xù)觸發(fā),待會我會給你們看一下區(qū)別

思考一下骤铃,什么時候使用防抖拉岁?什么時候使用節(jié)流呢?

  • resize scroll 還有用戶點擊按鈕提交ajax請求的時候惰爬,都可以使用debounce防抖來提升性能喊暖。當(dāng)用戶狂點擊提交請求的時候,我們在用戶點擊的時候不做出任何響應(yīng)撕瞧,等到用戶停止點擊之后我們再去提交陵叽。
  • 當(dāng)然,實現(xiàn)無限滾動的時候丛版,我們最好還是用節(jié)流來實現(xiàn)巩掺。我們總不能讓用戶滾動到底部之后停一下再去響應(yīng)事件吧。我們要做的應(yīng)該是在滾動期間每隔一定的時間段去響應(yīng)判斷滾動條是否已經(jīng)到達(dá)底部页畦。

三 手動實現(xiàn)防抖debounce

我知道目前實現(xiàn)得最好的是underscore.js的_debounce()方法胖替。但是作為小白,還是一步一步慢慢進(jìn)階吧豫缨。后期再認(rèn)真弄清楚源碼独令。

1)最初級的版本:來自高程三的例子

test.gif

var common = document.getElementById('common')   // 獲取頁面的左邊
var special = document.getElementById('special')   // 獲取頁面的右邊
// 這是執(zhí)行debounce的。
 window.onresize = function(){
   debounce(addlist ,delay)  //這一個設(shè)置了節(jié)流
   commonWay()   // 這個是普通的函數(shù)執(zhí)行
}
 function debounce(fn,delay) {     // 定義一個debounce函數(shù)
     clearTimeout(fn.timeid)
      fn.timeid = setTimeout(function() {
      fn()  
     },delay)
  }   
}    
function addlist () {                // 監(jiān)聽事件的響應(yīng)事件好芭,執(zhí)行dom操作燃箭。
   special.innerHTML+='<li>k</li>'  
}
function commonWay () {     // 這是執(zhí)行了普通的函數(shù)
  common.innerHTML+='<li>k</li>'  
}

其實就是給fn設(shè)置了一個屬性,timeid栓撞,記錄此時的定時器遍膜。在事件觸發(fā)的時候,會先清除定時器瓤湘,然后再設(shè)置一個定時器,也就是只要在事件觸發(fā)的時候恩尾,剛建立一個定時器就把它銷毀弛说,直到最后一次,事件觸發(fā)結(jié)束之后翰意,就執(zhí)行最后一次設(shè)置的定時器木人。

2)稍微升級一下:用閉包存儲變量
細(xì)心的你可能會發(fā)現(xiàn)信柿,通過給傳進(jìn)來的函數(shù)添加屬性來存儲定時器,是不太好的醒第,怎么說你一個函數(shù)參數(shù)傳遞進(jìn)來渔嚷,你去修改它的屬性。是不好的方式稠曼。
那么形病。我們可以用閉包了存儲當(dāng)前的定時器變量。修改如下:這里只將debounce的修改放下來霞幅。

var middle = debounce(addlist ,delay)    // 先執(zhí)行這個函數(shù)漠吻,返回一個函數(shù),存儲起來
window.addEventListener('resize', function () {
   middle()  //這一個設(shè)置了節(jié)流
   commonWay()   // 這個是普通的函數(shù)執(zhí)行
 })
/*
@fn 是要執(zhí)行的響應(yīng)操作事件
@delay 是延時的時間長度
*/
function debounce(fn,delay) {
  var timeid;   // 這里定義一個變量
  return function () {    // 閉包的使用司恳,返回一個函數(shù)途乃。
   clearTimeout(timeid)
   timeid = setTimeout(function() {
     fn() 
 },delay)
}   
}  

四 節(jié)流的實現(xiàn)

當(dāng)然,你會發(fā)現(xiàn)扔傅,單純的實現(xiàn)防抖debounce效果還是有所欠缺的耍共。如果我們在實現(xiàn)無限當(dāng)滾動的時候,用戶每次都要等到停止?jié)L動之后才能繼續(xù)加載數(shù)據(jù)猎塞,對于用戶的體驗是不是很不好试读。所以這個時候就可以用到節(jié)流了。

實現(xiàn)思想:事件第一次觸發(fā)的時候邢享,記錄函數(shù)執(zhí)行的時間鹏往,當(dāng)函數(shù)再一次執(zhí)行的時間間隔達(dá)到interval毫秒的時候,就執(zhí)行函數(shù)骇塘。也就是每隔一定的時間伊履,就執(zhí)行一次響應(yīng)函數(shù)。

// tottle的實現(xiàn)款违,也就是節(jié)流的實現(xiàn),就是設(shè)置了一個一開始函數(shù)運行的時間戳進(jìn)行執(zhí)行
function throttle (fn, delay, mustRunDelay,context) {
  var startTime, timestamp, timer;
  return function () {
    timestamp = +new Date()   // 先設(shè)置開始的時間
    clearTimeout(timer)
    if (!startTime) {
      startTime = timestamp    
    } 
    if (timestamp - startTime >= mustRunDelay) {
      fn.apply(context)
      startTime = timestamp 
    }else {
      timer = setTimeout(function () {
      fn.apply(context)    
    },delay)    
    }
   }
}
var middle = debounce(addlist ,delay)   // 先執(zhí)行函數(shù)唐瀑,保存匿名函數(shù)
 var middle2 = throttle(commonWay,1000,500)  // 再次執(zhí)行函數(shù),然后保存匿名函數(shù)
 window.addEventListener('resize', function () {
   middle()
   middle2() 
})
test.gif

最后插爹,用節(jié)流來實現(xiàn)無限滾動效果

// html 部分
<html>
<body>
   <div class='infinitScroll' id='list'>
      <div class='scroll'></div>
      <div class='scroll'></div>
      <div class='scroll'></div>
      <div class='scroll'></div>
    </div>
</body>
</html>
// js 部分 
function fetchdata () {   //  這里模擬數(shù)據(jù)的獲取哄辣。
  var Odiv = '<div class="scroll"></div>'
  var result = ''
  for (var i =0; i<20; i++) {
    result += Odiv
  }
  return result;  
}

function cal_set () {    // 判斷是否到達(dá)底部,到達(dá)底部就獲取數(shù)據(jù)
    var pixelsFromWindowBottomToBottom = document.body.scrollHeight - document.body.scrollTop - document.body.clientHeight
    if (pixelsFromWindowBottomToBottom < 300){
        myContainer.innerHTML += fetchdata()
    }
}
window.onscroll = throttle(cal_set, 300)   // 滾動監(jiān)聽
test.gif
當(dāng)然赠尾。我也用不加節(jié)流限制的力穗,實現(xiàn)了滾動的監(jiān)聽。實現(xiàn)的效果如下圖所示气嫁。雖然也是一樣可以實現(xiàn)無限滾動当窗,但是你可以看一下控制臺的輸出,稍微滾動一下就已經(jīng)輸出了上百條了寸宵。再看看上面的輸出崖面,其實只執(zhí)行了幾次而已元咙。
window.onscroll = cal_set
test.gif

當(dāng)然,現(xiàn)在也有實現(xiàn)這兩個效果的函數(shù)巫员,就是underscore的兩個函數(shù)庶香,debounce和throttle.因為我有稍微研究了一下,但是還有一些細(xì)節(jié)不是很懂简识。就不詳細(xì)講了赶掖。不過可以看一下下面分享的鏈接。韓遲子的underscore源碼詳解有做一些相關(guān)的講解财异。
當(dāng)然倘零,CSS-trick的鏈接也有很多相關(guān)的例子,大家可以直接上去看戳寸。

引用鏈接:

https://css-tricks.com/debouncing-throttling-explained-examples/
https://github.com/hanzichi/underscore-analysis/issues/22
https://keelii.github.io/2016/06/11/javascript-throttle/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呈驶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子疫鹊,更是在濱河造成了極大的恐慌袖瞻,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拆吆,死亡現(xiàn)場離奇詭異聋迎,居然都是意外死亡,警方通過查閱死者的電腦和手機枣耀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門霉晕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捞奕,你說我怎么就攤上這事牺堰。” “怎么了颅围?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵伟葫,是天一觀的道長。 經(jīng)常有香客問我院促,道長筏养,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任常拓,我火速辦了婚禮渐溶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弄抬。我一直安慰自己掌猛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布眉睹。 她就那樣靜靜地躺著荔茬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪竹海。 梳的紋絲不亂的頭發(fā)上慕蔚,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音斋配,去河邊找鬼孔飒。 笑死,一個胖子當(dāng)著我的面吹牛艰争,可吹牛的內(nèi)容都是我干的坏瞄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼甩卓,長吁一口氣:“原來是場噩夢啊……” “哼鸠匀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逾柿,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤缀棍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后机错,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爬范,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年弱匪,在試婚紗的時候發(fā)現(xiàn)自己被綠了青瀑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡萧诫,死狀恐怖斥难,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情财搁,我是刑警寧澤蘸炸,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站尖奔,受9級特大地震影響搭儒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜提茁,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一淹禾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茴扁,春花似錦铃岔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽智嚷。三九已至,卻和暖如春纺且,著一層夾襖步出監(jiān)牢的瞬間盏道,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工载碌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猜嘱,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓嫁艇,卻偏偏與公主長得像朗伶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子步咪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容