前端节流(throttle)和防抖动(debounce)
- 前端笔记
- 2022-01-26
- 1049热度
- 0评论
节流(throttle)
节流指的都是某个函数在一定时间间隔内只执行第一次回调。举个常见的节流案例:我们把某个表单的提交按钮——button 设成每三秒内最多执行一次 click 响应;当你首次点击后,函数会无视之后三秒的所有响应;三秒结束后,button 又恢复正常 click 响应功能,以此类推。
有什么用呢?通常,这类提交 button 的@click响应会给后端发送 api 请求,频繁的点击意味着频繁的请求(流量)——会给后端带来很大的压力;此外,这些回调请求返回后,往往会在前端响应其他事件(如刷新页面),可能导致页面不停的加载,影响用户体验。所以我们要给这个 button 添加节流函数,防止一些无意义的点击响应。
节流实现
节流的实现就要用到 js 高阶函数了。基础版 throttle 实现很简单:利用闭包记录前一次执行的时间戳,并判断本次点击和前一次点击的时间间隔,超过设定域值(如 3 秒)才响应函数,反之不响应:
const throttle = (cb, wait = 3000) =>{
let previous = 0;
return (...args) => {
const now = +new Date();
if( now - previous > wait ){
previous = now;
cb.apply(this, args);
}
}
}
例如滚动加载,滚动到底部了,数据正在加载,用户重复触发滚动到底部,这时就需要节流,没加载完之前,不会触发第二次
这里的cb
就是被执行的回调函数,wait
是设定的时间间隔。具体使用的时候,只要给常规监听的回调函数套一层throttle方法即可:
$button.addEventListener("click", throttle(cabllback));
还有一种常见的实现是做个定时器锁,只是会延后执行首次响应事件(如 3 秒后再执行回调);不过,同样可以确保特定时间间隔内只执行一次响应。
const throttle = (cb, wait=3000) => {
let timeId;
return (...args) => {
if( timeId ) return;
timeId = setTimeout(() => {
cb.apply(this, args);
timeId = undefined;
}, wait)
}
}
防抖动(debounce)
所谓的抖动就是浏览器频繁布局时,由于算力不足导致的页面颤动现象。防抖动就是利用类似于节流的手段——无视短时间内重复回调,避免浏览器发生抖动现象的技术。限流和防抖动在设计思想上一脉相承,只是限流是在某段时间内只执行首次回调,而防抖动通常是只执行末次回调。
比较常见的抖动场景是在自动索引的搜索设计上;当我们在搜索框内输入不同索引时,页面会频繁计算索引并渲染列表,以致产生抖动。但事实上在这类场景里,有价值的请求只会发生在用户停止输入后,通俗来说就是用户输入过程中的字符串不必当真。
function debounce(cb, wait = 3000) {
let timer = null
return (...args) => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
timer = null;
cb.apply(this, args)
}, wait)
}
}
很多库在实现 debounce 的时候还喜欢加第三个参数——immediate
:一个 boolean 参数,表示是否执行首次响应(默认是最后一次)。这算是防抖动和节流结合使用的实现了:
function debounce(cb, wait = 3000, immediate = false) {
let timeout;
return function(...args) {
let callNow = immediate && !timeout;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
if (!immediate) cb.apply(this, args);
}, wait);
if (callNow) cb.apply(this, args);
};
}
总结
- 节流: 控制高频事件执行次数
- 防抖:用户触发事件过于频繁,只要最后一次事件的操作
函数防抖是某一段时间内只执行一次;而函数节流是间隔时间执行,不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。
防抖是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,都会清除当前的 timer 然后重新设置超时调用,即重新计时。这样一来,只有最后一次操作能被触发。
节流是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。
demo:传入一个函数,返回的也是这个函数,调用这个函数三次后就不在调用
function transformer(func) {
let i = 3;
return function (...args) {
const context = this;
if (i > 0) {
i--;
return func.apply(context, args);
} else {
return;
}
}
}
虚拟滚动
前端虚拟滚动(Virtual Scrolling)是一种优化长列表渲染性能的技术,它的原理是只渲染可视区域内的元素,从而减少渲染开销,提高性能。具体实现时,可以将列表分成多个虚拟的块,只渲染当前可视区域内的块,其他块则不渲染。随着用户滚动列表,不断加载新的块,同时移除不可见的块,从而实现滚动效果。
虚拟滚动的原理可以分为两个方面:
- 块的划分:将长列表划分成多个虚拟的块,每个块包含一定数量的元素。块的大小通常根据可视区域的大小和元素的大小来确定,可以动态计算或手动设置。在初始渲染时,只渲染可视区域内的块,其他块则不渲染。
- 块的加载和移除:随着用户滚动列表,不断加载新的块,同时移除不可见的块,从而实现滚动效果。当用户滚动到新的块时,需要动态生成DOM元素并插入到可视区域内,同时移除不可见的块,以减少渲染开销。在加载和移除块时,需要进行一些优化,例如缓存块、预加载块等,以提高性能和用户体验。
需要注意的是,虚拟滚动的实现需要根据具体情况进行选择和优化,以达到最佳的性能和用户体验。同时,虚拟滚动也可能会带来一些问题,例如滚动条长度不准确、元素高度不一致等,需要在实现时进行充分考虑和测试。