本文引自:函数防抖和节流 做了一定的修改

在前端开发的过程中,我们经常会需要绑定一些持续触发的事件,但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数。

通常这种情况下我们怎么去解决的呢?一般来讲,防抖和节流是比较好的解决方案。

让我们先来看看在事件持续触发的过程中频繁执行函数是怎样的一种情况。

  1. <div id="content"
  2. style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"
  3. ></div>
  4. <script>
  5. let num = 1;
  6. let content = document.getElementById('content');
  7. function count() {
  8. content.innerHTML = num++;
  9. };
  10. content.onmousemove = count;
  11. </script>

在上述代码中,div 元素绑定了 mousemove 事件,当鼠标在 div(灰色)区域中移动的时候会持续地去触发该事件导致频繁执行函数。

函数的节流和防抖 - 图1

可以看到,在没有通过其它操作的情况下,函数被频繁地执行导致页面上数据变化特别快。所以,接下来让我们来看看防抖和节流是如何去解决这个问题的。

防抖(debounce)

所谓防抖,就是指触发事件后在 n 秒内函数后才会执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

在触发后,必须等待足够长的时间,才能执行某个函数,再次触发会导致需要重新等待。

防抖函数分为非立即执行版和立即执行版。

非立即执行版

  1. function debounce(func, wait) {
  2. let timer;
  3. return function (...args) {
  4. let context = this;
  5. timer && clearTimeout(timer);
  6. timer = setTimeout(() => {
  7. func.apply(context, args)
  8. }, wait);
  9. }
  10. }

非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

我们依旧使用上述绑定 mousemove 事件的例子,通过上面的防抖函数,我们可以这么使用

  1. content.onmousemove = debounce(count,1000);

函数的节流和防抖 - 图2

可以看到,在触发事件后函数 1 秒后才执行,而如果我在触发事件后的 1 秒内又触发了事件,则会重新计算函数执行时间。

立即执行版

  1. function debounce(func,wait) {
  2. let timer;
  3. return function (...args) {
  4. let context = this;
  5. timer ? clearTimeout(timer) : func.apply(context, args)
  6. timer = setTimeout(() => {
  7. timer = null; // 注意清空 timer
  8. }, wait)
  9. }
  10. }

立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。

函数的节流和防抖 - 图3

在开发过程中,我们需要根据不同的场景来决定我们需要使用哪一个版本的防抖函数,一般来讲上述的防抖函数都能满足大部分的场景需求。但我们也可以将非立即执行版和立即执行版的防抖函数结合起来。

封装结合版

  1. /**
  2. * @desc 函数防抖
  3. * @param func 函数
  4. * @param wait 延迟执行毫秒数
  5. * @param immediate true 表立即执行,false 表非立即执行
  6. */
  7. function debounce(func,wait,immediate) {
  8. let timer;
  9. return function (...args) {
  10. let context = this;
  11. timer && clearTimeout(timer);
  12. if (immediate) {
  13. !timer && func.apply(context, args)
  14. timer = setTimeout(() => {
  15. timer = null;
  16. }, wait)
  17. } else {
  18. timer = setTimeout(function(){
  19. func.apply(context, args)
  20. }, wait);
  21. }
  22. }
  23. }

防抖的用途

比如 popover 按钮上的提示框,鼠标移出按钮后,不会立即执行 hide,而是隔一段时间再执行,这时,只要移入到提示框中,提示框就不会隐藏。用于处理提示框与按钮之间存在间隙时,如何移入提示框的问题。

节流(throttle)

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。防止用户高频率的点击。

对于节流,同样有两种版本:立即执行和非立即执行。

立即执行版

与防抖的立即执行版,非常相像。

  1. function throttle(func, wait) {
  2. let timer;
  3. return function(...args) {
  4. let context = this;
  5. if (!timer) {
  6. func.apply(context, args)
  7. timer = setTimeout(() => {
  8. timer = null;
  9. }, wait)
  10. }
  11. }
  12. }
  1. content.onmousemove = throttle(count,1000);

函数的节流和防抖 - 图4

可以看到,在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次。

非立即执行版

非立即执行版与立即执行版,只是执行位置变化了

  1. function throttle(func, wait) {
  2. let timer;
  3. return function(...args) {
  4. let context = this;
  5. if (!timer) {
  6. timer = setTimeout(() => {
  7. timer = null;
  8. func.apply(context, args)
  9. }, wait)
  10. }
  11. }
  12. }

函数的节流和防抖 - 图5

可以看到,在持续触发事件的过程中,函数不会立即执行,并且每 1s 执行一次,在停止触发事件后,函数还会再执行一次。

封装结合版

  1. /**
  2. * @desc 函数节流
  3. * @param func 函数
  4. * @param wait 延迟执行毫秒数
  5. * @param immediate true 表立即执行,false 表非立即执行
  6. */
  7. function throttle(func, wait , immediate) {
  8. let timer;
  9. return function(...args) {
  10. let context = this;
  11. if (!timer) {
  12. immediate && func.apply(context, args)
  13. timer = setTimeout(() => {
  14. timer = null;
  15. !immediate && func.apply(context, args)
  16. }, wait)
  17. }
  18. }
  19. }