其實早就聽說了節(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)用而已惶我。
二 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)最初級的版本:來自高程三的例子
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()
})
最后插爹,用節(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)聽
當(dāng)然赠尾。我也用不加節(jié)流限制的力穗,實現(xiàn)了滾動的監(jiān)聽。實現(xiàn)的效果如下圖所示气嫁。雖然也是一樣可以實現(xiàn)無限滾動当窗,但是你可以看一下控制臺的輸出,稍微滾動一下就已經(jīng)輸出了上百條了寸宵。再看看上面的輸出崖面,其實只執(zhí)行了幾次而已元咙。
window.onscroll = cal_set
當(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/