防抖和节流是前端开发中经常使用的一种优化手段,它们都被用来控制一段时间内方法执行的次数,可以为我们节省大量不必要的开销
防抖(debounce)
当我们需要及时获知窗口大小变化时,我们会给window绑定一个resize函数,像下面这样:
window.addEventListener('resize', () => {
console.log('resize')
});
我们会发现,即使是极小的缩放操作,也会打印数十次resize,也就是说,如果我们需要在onresize函数中搞一些小动作,也会重复执行几十次。但实际上,我们只关心鼠标松开,窗口停止变化的那一次resize,这时候,就可以使用debounce优化这个过程:
const handleResize = debounce(() => {
console.log('resize');
}, 500);
window.addEventListener('resize', handleResize);
行上面的代码(你得有现成的debounce函数),在停止缩放操作500ms后,默认用户无继续操作了,才会打印resize
这就是防抖的功效,它把一组连续的调用变为了一个,最大程度地优化了效率
再举一个防抖的常见场景:
搜索栏常常会根据我们的输入,向后端请求,获取搜索候选项,显示在搜索栏下方。如果我们不使用防抖,在输入“debounce”时前端会依次向后端请求"d"、"de"、"deb"..."debounce"的搜索候选项,在用户输入很快的情况下,这些请求是无意义的,可以使用防抖优化
观察上面这两个例子,我们发现,防抖非常适于只关心结果,不关心过程如何的情况,它能很好地将大量连续事件转为单个我们需要的事件
为了更好理解,下面提供了最简单的debounce实现:返回一个function,第一次执行这个function会启动一个定时器,下一次执行会清除上一次的定时器并重起一个定时器,直到这个function不再被调用,定时器成功跑完,执行回调函数
const debounce = function(func, wait) {
let timer;
return function() {
!!timer && clearTimeout(timer);
timer = setTimeout(func, wait);
};
};
那如果我们不仅关心结果,同时也关心过程呢?
节流(throttle)
节流让指定函数在规定的时间里执行次数不会超过一次,也就是说,在连续高频执行中,动作会被定期执行。节流的主要目的是将原本操作的频率降低
实例:
我们模拟一个可无限滚动的feed流
<div id="wrapper">
<div class="feed"></div>
<div class="feed"></div>
<div class="feed"></div>
<div class="feed"></div>
<div class="feed"></div>
</div>
#wrapper {
height: 500px;
overflow: auto;
}
.feed {
height: 200px;
background: #ededed;
margin: 20px;
}
const wrapper = document.getElementById("wrapper");
const loadContent = () => {
const {
scrollHeight,
clientHeight,
scrollTop
} = wrapper;
const heightFromBottom = scrollHeight - scrollTop - clientHeight;
if (heightFromBottom < 200) {
const wrapperCopy = wrapper.cloneNode(true);
const children = [].slice.call(wrapperCopy.children);
children.forEach(item => {
wrapper.appendChild(item);
})
}
}
const handleScroll = throttle(loadContent, 200);
wrapper.addEventListener("scroll", handleScroll);
可以看到,在这个例子中,我们需要不停地获取滚动条距离底部的高度,以判断是否需要增加新的内容。我们知道,srcoll同样也是种会高频触发的事件,我们需要减少它有效触发的次数。如果使用的是防抖,那么得等我们停止滚动之后一段时间才会加载新的内容,没有那种无限滚动的流畅感。这时候,我们就可以使用节流,将事件有效触发的频率降低的同时给用户流畅的浏览体验。在这个例子中,我们指定throttle的wait值为200ms,也就是说,如果你一直在滚动页面,loadCotent函数也只会每200ms执行一次
同样,这里有throttle最简单的实现,当然,这种实现很粗糙,有不少缺陷(比如没有考虑最后一次执行),只供初步理解使用:
const throttle = function (func, wait) {
let lastTime;
return function () {
const curTime = Date.now();
if (!lastTime || curTime - lastTime >= wait) {
lastTime = curTime;
return func();
}
}
}