最近去面试,又遇到面试官问我防抖与节流了,而明明前几天就看过手写代码,却写不出来。有时候我在想,是不是自己太笨了
回归正题
防抖
先不说概念,按自己的理解,在单反里,有防抖机制。因为人在拿着单反的时候会手抖(单反重),按下快门的瞬间,照片会糊,所以有防抖机制,以防止新手把照片拍糊
单反中的防抖是防止抖动,让人拍出清晰的照片,JavaScript 中的防抖是为了什么?
同理,它的作用也是防止抖动。试想当你频繁触发一个事件时,就会引起不必要的性能损失,那么让该事件在停止触发后再触发,以此减少部分性能
防抖的定义
防抖就是要延迟执行,你一直操作触发事件一直不执行,当你停止操作等待多少秒后才执行
也就是说不管事件触发频率有多高,一定在事件触发 n 秒后执行。如果在事件触发的 n 秒又触发了这个事件,那就以新事件的事件为准,n 秒后才执行。总之,要等你触发完事件 n 秒内不再触发事件,它才执行
手写防抖
根据定义,我们知道要在时间 n 秒后执行,那么我们就用定时器来实现
function debounce(event, wait) { let timer = null; return function (...args) { clearTimeout(timer); // 清除setTimeout,使其回调函数不执行 timer = setTimeout(() => { event.apply(this, args) }, wait) } }
代码很简单,即当还在触发事件时,就清除 timer,使其在 n 秒后执行,但此写法首次不会立即执行,为其健壮性,需加上判断是否第一次执行的第三个参数 flag,判断其是否立即执行
function debounce(event, wait, flag) { let timer = null; return function (...args) { clearTimeout(timer) if (!timer && flag) { event.apply(this, args) } else { timer = setTimeout(() => { event.apply(this, args) }, wait) } } }
防抖场景
窗口大小变化,调整样式
window.addEventListener('resize', debounce(handleResize, 200))
搜索框,输入后1000毫秒搜索
debounce(fetchSelectData, 300)
表单验证,输入 1000 毫秒后验证
debounce(validator, 1000)
防抖帝王库
两大工具库都有防抖源码,可供参考
lodash-debounce
underscore-debounce
节流
顾名思义,一节一节的流,就好似控制水阀,在事件不断触发的过程中,固定时间内执行一次事件
手写节流
因为是固定时间内执行一次时间,所以我们有两种实现方法,一用时间戳,二用定时器
时间戳
function throttle(event, wait) { let pre = 0; return function (...args){ if (new Date() - pre > wait) { // 当 n 秒内不重复执行 pre = new Date(); event.apply(this, args) } } }
使用时间戳虽然能实现节流,但是最后一次事件不会执行
定时器
function throttle(event, wait) { let timer = null; return function (...args) { if (!timer) { timer = setTimeout(() => { timer = null; event.apply(this, args) }, wait) } } }
使用定时器实现节流,虽然最后一次能触发,但是第一次不会触发
时间戳 + 定时器
为解决第一次和最后一次都可以触发,把两者结合起来
function throttle(event, wait) { let pre = 0, timer = null; return function (...args) { if (new Date() - pre > wait) { clearTimeout(timer); timer = null; pre = new Date(); event.apply(this, args) } else { timer = setTimeout(() => { event.apply(this, args) }, wait) } } }
节流场景
scroll 滚动
window.addEventListener('scroll', throttle(handleScroll, 200))
input 动态搜索
throttle(fetchInput, 300)
节流帝王库
lodash-throttle
underscore-throttle
总结
防抖:只执行最后一次。事件持续触发,但只有等事件停止触发后 n 秒后才执行函数
节流:控制执行频率。持续触发,每 n 秒执行一次函数
对比图:
防抖节流对比图
线上demo(司徒正美的demo):防抖节流
参考资料
[1]
lodash-debounce: https://github.com/lodash/lodash/blob/master/debounce.js
[2]
underscore-debounce: https://github.com/jashkenas/underscore/blob/master/modules/debounce.js
[3]
lodash-throttle: https://github.com/lodash/lodash/blob/master/throttle.js
[4]
underscore-throttle: https://github.com/jashkenas/underscore/blob/master/modules/throttle.js
[5]
防抖节流: https://azhubaby.com/demo/防抖与节流/index.html
[6]
awesome-coding-js: www.conardli.top/docs/JavaScript/节流.html
[7]
防抖节流场景及应用: https://www.zoo.team/article/anti-shake-throttle
[8]
函数防抖与函数节流: https://zhuanlan.zhihu.com/p/38313717