某天下午,临近下班之际,公司的一位前端实习生打破了我 ~~快乐摸鱼,等待下班~~ 认真工作的状态,说他碰到一个问题,让我帮忙给看看,虽说我也是今年刚毕业应届生,但是!我寻思这咱好歹也有过将近一年的实习经历的男人,想着他实习应该也不会问多难得问题,就过去瞅了瞅。

我走过去一看,原来啊,他想问的是一个关于函数节流的问题,大概就是在指定元素上绑定鼠标 `onmousemove` 事件,然后在那个元素上移动鼠标,每隔500ms输出一次鼠标的X,Y坐标。

我当时就想到这就是用函数的节流么,然后动手写,手搭在键盘上愣了两秒,对 没错,我自己也忘记怎么写了。这些要是让当时我面试的时候写,我肯定能写出来,但是现在我工作了。。。。

裆燃,实际项目中,可能更多的使用Lodash工具库提供的debounced函数,本文只是给自己加深印象,分清楚防抖和节流以及它们的使用场景而已。

函数的防抖和节流

在前端开发中有一部分的用户行为会频繁的触发某些事件执行,而对于DOM操作、资源加载等耗费性能的处理,很可能导致界面卡顿,甚至浏览器的崩溃。函数节流(throttle)和函数防抖(debounce)就是为了解决类似需求应运而生的。防抖与节流是最常用的高频触发优化的方式,比如在开发中防止重复提交、在下拉加载更多的时候防止重复请求的问题等。

防抖

防抖是指当持续触发事件时,设定的事件段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一个用户一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行一次。

就像电梯一样,假设电梯门是三秒自动关闭,第一个人进入电梯后,电梯开始倒计时三秒后关门,当电梯感应到又有人进入电梯时,则当第二个人进入后,重新开始倒计时三秒,如果三秒内没有检测到有人进入电梯的话,则关门,去指定的楼层。道理就是这么个道理。

简单来说就是在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新开始计时。通常使用的场景是:输入查询/校验,只需要在输入完成后做一次查询/校验即可。

根据函数防抖思路实现简单的防抖代码:

function debounce(func, delay) {
  let timer = null // 创建一个标记用来存放定时器的返回值
  return function () {
    let context = this
    if (timer) clearTimeout(timer) // 每次执行的时候把前一个定时器清除掉
    timer = setTimeout(() => {
      // 重新计时
      func.apply(context, arguments)
    }, delay)
  }
}
let count = 0;
function winResize() {
  console.log(`"window Resize!"${count++}`)
}
window.addEventListener("resize", debounce(winResize, 666))

运行:

节流

节流相比防抖就比较好理解了,即使得一定时间内只触发一次函数,每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,常用的场景有: 屏幕滚动或者 resize 事件,原理是通过判断是否有延迟调用函数未执行。

函数节流的目的,是为了限制函数一段时间内只能执行一次。因此,定时器实现节流函数通过使用定时任务,延时方法执行。在延时的时间内,方法若被触发,则直接退出方法。从而实现函数一段时间内只执行一次。

function throttle(fn, delay = 100) {
  //首先设定一个变量,在没有执行时为null
  let timer = null
  return function () {
    // 如果这个定时器存在时,则表示定时器正在运行中,直接return
    if (timer) return
    timer = setTimeout(() => {
      fn.apply(this, arguments)
      timer = null
    }, delay)
  }
}
let count = 0;
function winResize() {
  console.log(`"window Resize!"${count++}`)
}
window.addEventListener("resize", throttle(winResize, 666))

运行:

两者的区别

相同点:

  • 都是通过setTimeout实现
  • 目的都是通过降低执行频率,从而节省资源
    不同点:
  • 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeoutsetTimeout实现。
  • 函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。
  • 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次

例如,都设置时间频率为500ms,在2秒时间内,频繁触发函数,节流:每隔 500ms 就执行一次;防抖:不管调用多少次方法,在2s后,只会执行一次

最后

最后再完成一下那位实习生的问题

运行结果:

上面的代码也只是我一个简单实现,为了自己更理解防抖和节流的概念和实现大概的实现过程,生产中建议使用Lodash它们的库,毕竟有这么多人在用,出bug的机会比较少。

-------- 本文结束感谢阅读 --------

集中精神,以气御剪