欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

前端值得一探究竟特辑 --- 防抖与节流

程序员文章站 2022-05-06 09:43:14
...

防抖

防抖函数的作用就是控制函数在一定时间内的执行次数。防抖意味着N秒内函数只会被执行一次,如果N秒内再次被触发,则重新计算延迟时间。
以乘车刷卡的情景举例,只要乘客不断地在刷卡,司机师傅就不能开车,乘客刷卡完毕之后,司机会等待几分钟,确定乘客坐稳再开车。如果司机在最后等待的时间内又有新的乘客上车,那么司机等乘客刷卡完毕之后,还要再等待一会,等待所有乘客坐稳再开车。

1. 防抖的应用

  1. 搜索框输入即查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
  2. 浏览器窗口缩放,resize事件(如窗口停止改变大小之后重新计算布局)等。

2. 防抖的实现

思路:每次触发事件时都取消之前的延时调用方法。

// HTML
  <input id="dom" />
// JS
  var dom = document.getElementById("dom");
  
  dom.addEventListener("input", debounce(() => {
      console.log("假装我是一个请求FUNC");
    }, 1000));
    
  function debounce(fn, wait) {
    var time = null;
    return function () {
      clearTimeout(time);
      time = setTimeout(() => {
        fn.apply(this, arguments)
      }, wait);
    }
  }

陷阱:确保debounce函数的this(上下文还是div)
setTimeout第一个参数使用箭头函数且手动将this绑定到fn上,确保fn的上下文是div,缺一不可,否则的话 this为window和直接 fn()调用是一样的效果,因为他们的this都是window,如果再fn内部要使用事件对象,是获取不到的。

      time = setTimeout(() => {
        console.log(this); //div
        fn.apply(this, arguments)//确保fn函数的this(上下文还是div),所有setTimeOut第一个参数使用箭头函数而不是普通函数,确保this是div,并手动将this绑定到fn上
      }, wait);

3. 详解执行过程

首先,页面加载,代码解析到脚本部分

  1. 函数和变量声明提升
    前端值得一探究竟特辑 --- 防抖与节流

  2. 代码自上而下执行

    dom.addEventListener("input", debounce(() => {
          console.log("假装我是一个请求FUNC");
        }, 1000));
    

    这里会调用debounce, 执行环境为debounce开辟一个栈内存,最终addEventListener第二个参数(回调函数)存储的是堆地址H10087。由于回调函数中占用了time、fn、wait,造成了栈内存S10086执行完毕却无法进行内存回收,形成闭包。
    前端值得一探究竟特辑 --- 防抖与节流

  3. 事件触发,addEventListener调用回调函数,执行环境为回调函数开辟一个栈内存S1008701,在代码执行过程中,又调用了setTimeOut,环境为setTimeOut开辟一个栈内存S100870101,setTimeOut第一个参数(回调函数)存储的是堆地址H1008702。此时栈内存S10086中wait依旧是1000,随时间递减的是setTimeOut函数内部的形参。
    前端值得一探究竟特辑 --- 防抖与节流

  4. 若wait还没有置0,setTimeOut第一个参数(回调函数堆内存H1008702)还没有被触发,事件再次触发,addEventListener再次调用回调函数(堆内存 H10087)。clearTimeout(time);的time是栈内存栈 S10086/ debounce执行中的time,存储着上一轮回调函数执行的setTimeOut手柄。在此上一轮的setTimeOut将被清除,time将存储此轮回调函数执行的setTimeOut手柄。前端值得一探究竟特辑 --- 防抖与节流

节流

节流的作用是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行,如果这个单位时间内多次触发函数,只能有一次生效。每次触发事件时都判断当前是否有等待执行的延时函数

1. 节流的应用

  1. 按钮点击事件
  2. 拖拽事件
  3. onScoll
  4. 计算鼠标移动的距离(mousemove)

2. 防抖的实现

每次触发事件时都查询是否有等待执行的延时函数。

// HTML
    <input id="dom" type="button" value="提交" />
// JS
    var dom = document.getElementById("dom");
    var i = 1;
    dom.addEventListener("click", throttle((e) => {
        console.log("假装我是一个请求FUNC");
        console.log(Date());
    }, 1000));
    function throttle(fn, wait) {
        var time = null;
        var previous = 0;
        return function () {
            console.log('节流');
            var now = new Date();
            if (now - previous > wait) {
                fn.apply(this, arguments);
                previous = now;
            }
        }
    }

无论我点击多少次提交,始终限定在1秒内只提交1次。
前端值得一探究竟特辑 --- 防抖与节流