函数节流与函数防抖

146 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

在公司有个需求需要写个简单的防抖,就简单写一些防抖和节流
最后主要讲述运用过程中出现的问题

函数节流与函数防抖

1.1 相关理解

  1. 事件频繁触发可能造成的问题?

    • 一些浏览器事件:window.onresize、window.mousemove等,触发的频率非常高,会造成界面卡顿
    • 如果向后台发送请求,频繁触发,对服务器造成不必要的压力
  2. 函数节流(throttle)

  • 理解:

    • 在函数需要频繁触发时: 函数执行一次后,只有大于设定的执行周期后才会执行第二次
    • 适合多次事件按时间做平均分配触发
  • 场景:

    • 窗口调整(resize)
    • 页面滚动(scroll)
    • DOM 元素的拖拽功能实现(mousemove)
    • 抢购疯狂点击(click)
  1. 函数防抖(debounce)
  • 理解:

    • 在函数需要频繁触发时: 在规定时间内,只让最后一次生效,前面的不生效。
    • 适合多次事件一次响应的情况
  • 场景:

    • 输入框实时搜索联想(keyup/input)
  1. 区别函数节流与防抖

1.2 api说明

  1. throttle() 节流

    • 语法: throttle(callback, wait)
    • 功能: 创建一个节流函数,在 wait 毫秒内最多执行 callback 一次
  2. debounce() 防抖

    • 语法: debounce(callback, time)
    • 功能: 创建一个防抖动函数,该函数会从上一次被调用后,延迟 time 毫秒后调用 callback

1.3 编码实现

函数节流:

//utils.js
/* 
实现函数节流
- 语法: throttle(callback, wait)
- 功能: 创建一个节流函数,在 wait 毫秒内最多执行 `callback` 一次
*/
function throttle(callback, wait) {
  let start = 0
  // 返回一个事件监听函数(也就是节流函数)
  return function (event) {
    console.log('throttle event')
    // 只有当距离上次处理的时间间隔超过了wait时, 才执行处理事件的函数
    const current = Date.now()
    if ( current - start > wait) {
      callback.call(this, event) // 需要指定this和参数
      start = current
    }
  }
}

函数防抖:

//utils.js
/* 
实现函数防抖
- 语法: debounce(callback, time)
- 功能: 创建一个防抖动函数,该函数会从上一次被调用后,延迟 `time` 毫秒后调用 `callback`
*/
function debounce(callback, time){
    //定时器变量
    let timeId = null;
    //返回一个函数
    return function(e){
        //判断
        if(timeId !== null){
            //清空定时器
            clearTimeout(timeId);
        }
        //启动定时器
        timeId = setTimeout(() => {
            //执行回调
            callback.call(this, e);
            //重置定时器变量
            timeId = null;
        }, time);
    }
}

1.4 测试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>函数节流与防抖</title>
</head>
<body>

  <button id="handle">正常处理</button>
  <button id="throttle">测试函数节流</button>
  <button id="debounce">测试函数防抖</button>
  
  <script src="../dist/utils.js"></script>

  <script>
    /* 处理点击事件的回调函数 */
    function handleClick(event) { // 处理事件的回调
      console.log('处理点击事件', this, event)
    }

    document.getElementById('handle').onclick = handleClick
    document.getElementById('throttle').onclick = throttle(handleClick, 2000)
    document.getElementById('debounce').onclick = debounce(handleClick, 2000)
  </script>

</body>
</html>

问题出错解决

抖动函数 以上在编写看似没毛病,但是react项目中运行,运行发现timeout定时器竟然全部都运行了,怎么回事!!!,明明是按照标准写的呀,只是换了事件绑定方式,为啥没有效果。

let timeoutId = -1 //用来保存定时器任务的标识id 问题出在这里

验证发现问题所在:

原来是调用防抖函数事件时每次点击都会运行一次,导致每次的储存定时器id的timeout变量每次都会被赋值成null,导致下面清除定时器时按照id找不到定时器, 所以所有定时器都没有被清除。

解决方法:

需要把timeout变量赋值放到debounce函数的外面,这样每次点击运行函数时不会对定时器id进行赋值,也就不会清除不了定时器了。

正确函数写法:

//定时器变量
let timeId = null;//需要把用来保存定时器任务的标识id变量赋值放到debounce函数的外面
 function debounce(callback, time){
    
    //返回一个函数
    return function(e){
        //判断
        if(timeId !== null){
            //清空定时器
            clearTimeout(timeId);
        }
        //启动定时器
        timeId = setTimeout(() => {
            //执行回调
            callback.call(this, e);
            //重置定时器变量
            timeId = null;
        }, time);
    }
}