其实早就听说了节流和防抖的解决方案。这两个的目的都是为了提升页面中监听事件的性能。才疏学浅,如果有错误,也希望各位指正。
一 为什么会出现debounce和throttle
在2011年的时候,有一篇报告这样说:当你向下滚动twitter页面的时候,越向下滚动就会发现页面会越来越卡顿甚至无响应。John Resig 就在一篇博客中提到,在一些高触发的事件如scroll,resize事件中,直接在监听事件内部做出响应是非常不好的举措。 https://johnresig.com/blog/learning-from-twitter/
相信,你也写过这类dom事件的监听操作,也应该清楚地知道,当你监听scroll,resize事件的时候,事件触发的频率是非常高的,(具体的时间我没有考量)。如果你监听事件直接每次都进行相关的dom操作,消耗是非常大的。看一个pen上的例子。
下面这个是监听scroll事件的操作,在监听滚动事件的时候,数字自动累计增加。
可以看到,在每一次滚动的时候,事件触发的频率是非常高的。
所以,就有了节流和防抖机制的出现,其实这两个也没有什么特殊的,只不过是定时器的应用而已。
二 debounce 和 throttle是什么 ?
debounce,防抖,其实就是通过设置定时器,让高频触发的事件在触发结束之后再做出相关响应。只执行一次。
throttle,节流。其实也是通过设置定时器,让高频连续触发的事件每隔一定的时间长度之后再做出响应。是有规律的时间间隔去执行。
什么意思呢? 看一下上面的gif图,原本是一直在触发的,那么我们可以通过设置debounce防抖,达到当滚动停止之后,再触发事件。
那么?是否也可以通过设置throttle节流来实现?throttle其实就是不要在事件结束之后再响应,而是在这个时间段内持续触发,待会我会给你们看一下区别
思考一下,什么时候使用防抖?什么时候使用节流呢?
- resize scroll 还有用户点击按钮提交ajax请求的时候,都可以使用debounce防抖来提升性能。当用户狂点击提交请求的时候,我们在用户点击的时候不做出任何响应,等到用户停止点击之后我们再去提交。
- 当然,实现无限滚动的时候,我们最好还是用节流来实现。我们总不能让用户滚动到底部之后停一下再去响应事件吧。我们要做的应该是在滚动期间每隔一定的时间段去响应判断滚动条是否已经到达底部。
三 手动实现防抖debounce
我知道目前实现得最好的是underscore.js的_debounce()方法。但是作为小白,还是一步一步慢慢进阶吧。后期再认真弄清楚源码。
1)最初级的版本:来自高程三的例子
var common = document.getElementById('common') // 获取页面的左边
var special = document.getElementById('special') // 获取页面的右边
// 这是执行debounce的。
window.onresize = function(){
debounce(addlist ,delay) //这一个设置了节流
commonWay() // 这个是普通的函数执行
}
function debounce(fn,delay) { // 定义一个debounce函数
clearTimeout(fn.timeid)
fn.timeid = setTimeout(function() {
fn()
},delay)
}
}
function addlist () { // 监听事件的响应事件,执行dom操作。
special.innerHTML+='k '
}
function commonWay () { // 这是执行了普通的函数
common.innerHTML+='k '
}
其实就是给fn设置了一个属性,timeid,记录此时的定时器。在事件触发的时候,会先清除定时器,然后再设置一个定时器,也就是只要在事件触发的时候,刚建立一个定时器就把它销毁,直到最后一次,事件触发结束之后,就执行最后一次设置的定时器。
2)稍微升级一下:用闭包存储变量
细心的你可能会发现,通过给传进来的函数添加属性来存储定时器,是不太好的,怎么说你一个函数参数传递进来,你去修改它的属性。是不好的方式。
那么。我们可以用闭包了存储当前的定时器变量。修改如下:这里只将debounce的修改放下来。
var middle = debounce(addlist ,delay) // 先执行这个函数,返回一个函数,存储起来
window.addEventListener('resize', function () {
middle() //这一个设置了节流
commonWay() // 这个是普通的函数执行
})
/*
@fn 是要执行的响应操作事件
@delay 是延时的时间长度
*/
function debounce(fn,delay) {
var timeid; // 这里定义一个变量
return function () { // 闭包的使用,返回一个函数。
clearTimeout(timeid)
timeid = setTimeout(function() {
fn()
},delay)
}
}
四 节流的实现
当然,你会发现,单纯的实现防抖debounce效果还是有所欠缺的。如果我们在实现无限当滚动的时候,用户每次都要等到停止滚动之后才能继续加载数据,对于用户的体验是不是很不好。所以这个时候就可以用到节流了。
实现思想:事件第一次触发的时候,记录函数执行的时间,当函数再一次执行的时间间隔达到interval毫秒的时候,就执行函数。也就是每隔一定的时间,就执行一次响应函数。
// tottle的实现,也就是节流的实现,就是设置了一个一开始函数运行的时间戳进行执行
function throttle (fn, delay, mustRunDelay,context) {
var startTime, timestamp, timer;
return function () {
timestamp = +new Date() // 先设置开始的时间
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) // 先执行函数,保存匿名函数
var middle2 = throttle(commonWay,1000,500) // 再次执行函数,然后保存匿名函数
window.addEventListener('resize', function () {
middle()
middle2()
})
最后,用节流来实现无限滚动效果
// html 部分
// js 部分
function fetchdata () { // 这里模拟数据的获取。
var Odiv = ''
var result = ''
for (var i =0; i<20; i++) {
result += Odiv
}
return result;
}
function cal_set () { // 判断是否到达底部,到达底部就获取数据
var pixelsFromWindowBottomToBottom = document.body.scrollHeight - document.body.scrollTop - document.body.clientHeight
if (pixelsFromWindowBottomToBottom < 300){
myContainer.innerHTML += fetchdata()
}
}
window.onscroll = throttle(cal_set, 300) // 滚动监听
当然。我也用不加节流限制的,实现了滚动的监听。实现的效果如下图所示。虽然也是一样可以实现无限滚动,但是你可以看一下控制台的输出,稍微滚动一下就已经输出了上百条了。再看看上面的输出,其实只执行了几次而已。
window.onscroll = cal_set
当然,现在也有实现这两个效果的函数,就是underscore的两个函数,debounce和throttle.因为我有稍微研究了一下,但是还有一些细节不是很懂。就不详细讲了。不过可以看一下下面分享的链接。韩迟子的underscore源码详解有做一些相关的讲解。
当然,CSS-trick的链接也有很多相关的例子,大家可以直接上去看。
引用链接:
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/