背景
在实际开发中,我们会遇到这么一个场景:我们需要注册一个事件,但有时候这个事件会在短时间内频繁触发,事件频繁的执行会导致浏览器进行大量的计算而引发页面卡顿假死的情况,为些我们需要通过一些手段来解决这个问题,所以就有了防抖和节流这两个技术。
函数防抖和函数节流,两者都是优化高频率执行js
代码的一种手段。
防抖
函数防抖,是指触发高频事件n秒后函数会执行一次,如果n秒内高频事件被再次触发,则重新计算时间;在整个过程中,事件函数只会被执行一次。类似于游戏中的buff,如果已经有了buff,再拿一个相同的buff会重新计时。
// 函数防抖 let timer = null; document.getElementById("debounce").onscroll = function() { clearTimeout(timer); // 清除未执行的代码,重置回初始化状态 timer = setTimeout(() => { console.log("函数防抖"); }, 1000); };
函数防抖的要点:也需要一个定时器来辅助实现代码延迟执行。如果计时未完之前,方法被多次触发,则清除上次记录的定时器标记,重新开始。
- 若计时完毕,没有继续触发方法,则执行逻辑代码。
- 监听
id
为debounce
元素的滚动事件,首先就是清除上次未执行的setTimeout
的引用timer
clearTimeout
方法,允许传入无效的值。所以直接执行clearTimeout
即可。- 将需要执行的代码放入
setTimeout
定时器中,再返回定时器引用给timer
缓存。 - 如果倒计时结束,没有新的方法触发滚动事件,则执行
setTimeout
中的代码。 - 函数防抖的原理,就是巧用
setTimeout
做缓存池,并且可以轻易地清除待执行代码。
为了避免全局变量污染,这里推荐闭包的写法:
// 首次不立即执行 function debounce(func, wait) { let timer = null; return function () { clearTimeout(timer); timer = setTimeout(function () { func() }, wait); } } document.getElementById("debounce").onscroll = debounce(() => { console.log('函数防抖'); }, 1000)
函数防抖的应用场景
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
节流
函数节流:是指在高频事件触发期间,n秒内函数只会执行一次。比如游戏中英雄的技能CD,当CD还没好时,无法使用技能。
// 函数节流 let isOk = true; document.getElementById("throttle").onscroll = function(){ if(!isOk){ // 判断是否已空闲,如果在执行中,则直接return return; } isOk = false; setTimeout(function(){ console.log("函数节流"); isOk = true; }, 1000); }
函数节流的要点:声明一个标志位,设置执行的时间间隔,记录当前代码是否在执行,如果空闲,则可以正常触发方法执行,反之则取消这次方法执行,直接return
。
- 监听
id
为throttle
元素的滚动事件。当isOk
为true
,代表当前滚动处理事件是空闲的,可以使用。然后下一步的操作就是isOk = false
。这样其他请求执行滚动事件的方法,就被return
。 setTimeout
设置1000ms
时间间隔,执行定时器中的回调函数,释放标志位,允许执行下一次滚动事件。
这里也给出闭包的写法:
// 函数节流 function throttle(func, wait) { let isOk = true; return function () { if(!isOk){ // 判断是否已空闲,如果在执行中,则直接return return; } isOk = false; setTimeout(() => { func() isOk = true; }, wait); } } document.getElementById("throttle").onscroll = debounce(() => { console.log('函数节流'); }, 1000)
「【注】」 节流函数并不止上面这种实现方案, 例如可以完全不借助setTimeout
,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。也可以直接将setTimeout
的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行func
之后消除定时器表示激活,原理都一样。
函数节流的应用场景
- 滚动加载,加载更多或滚到底部监听
- 百度搜索框,搜索联想功能
- 高频点击提交,表单重复提交