JS高级 之 防抖 debounce - throttle 节流

目录

一、防抖 debounce

1. 概念

2. 应用场景

3. 使用 underscore 实现防抖

01 - 代码

02 - 效果

4. 实现

01 - 基本实现

        代码

        效果

02 - 优化 => this 和 参数绑定

        代码

        效果 

03 - 优化 => 取消功能

        代码

        效果

04 - 优化 => 立即执行功能

        代码

        效果

05 - 优化 => 获取返回值

        代码

06 - 最终版本

二、节流 throttle 

1. 概念

2. 应用场景

3. 使用 underscore 实现节流

01 - 代码

02 - 效果

4. 实现

01 - 基本实现

        代码

        效果

02 - 优化 => this 和 参数的绑定

        代码

        效果

03 - 优化 => 立即执行功能

        代码

        效果

04 - 优化 => 获取返回值

        代码

05 - 最终版本

5. 第二种方法实现节流


一、防抖 debounce

1. 概念

  • 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间
  • 当事件密集触发时,函数的触发会被频繁的推迟,并把上一次的给取消掉
  • 只有等待了一段时间也没有事件触发,才会真正的执行响应函数

2. 应用场景

  • 输入框中频繁的输入内容,搜索或者提交信息
  • 频繁的点击按钮,触发某个事件
  • 监听浏览器滚动事件,完成某些特定操作
  • 用户缩放浏览器的resize事件

3. 使用 underscore 实现防抖

01 - 代码

<body>
  <input type="text" />

  <!-- 1. CDN引入: 网络的js文件 -->
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.4/underscore-umd-min.js"></script>

  <script>
    // 2.获取input元素
    const inputEl = document.querySelector('input');

    // 3.防抖处理代码
    let counter = 1;
    inputEl.oninput = _.debounce(function () {
      console.log(`发送网络请求${counter++}:`, this.value);
    }, 1000);
  </script>
</body>

02 - 效果

4. 实现

01 - 基本实现

        代码

<body>
  <input type="text" />
  <script>
    function starDebounce(fn, delay) {
      // 1.用于记录上一次事件触发的timer
      let timer = null;
      // 2. 返回新的函数
      return function () {
        // 3. 如果有再次触发(更多次触发)事件, 那么取消上一次的事件
        if (timer) clearTimeout(timer);
        // 4. 绑定当前事件
        timer = setTimeout(() => {
          // 5. 延迟后执行
          fn();
          // 6. 执行后把当前定时器删除
          timer = null;
        }, delay);
      };
    }
  </script>
  <script>
    const inputDom = document.querySelector('input');
    let counter = 1;
    inputDom.oninput = starDebounce(function () {
      console.log(`发送网络请求${counter++}`);
    }, 1000);
  </script>
</body>

        效果

02 - 优化 => this 和 参数绑定

        代码

<body>
  <input type="text" />
  <script>
    function starDebounce(fn, delay) {
      let timer = null;
      // 1. 返回新的函数,此时这个函数中的this为绑定的dom对象 => 相当于 inputDom的oninput指向这个函数
      // 2. 接受参数
      return function (...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          // 3. 绑定this,同时把参数传递给fn
          fn.apply(this, args);
          timer = null;
        }, delay);
      };
    }
  </script>
  <script>
    const inputDom = document.querySelector('input');
    let counter = 1;
    inputDom.oninput = starDebounce(function (e) {
      console.log(`发送网络请求 :${counter++} => ${this.value} `, e);
    }, 1000);
  </script>
</body>

        效果 

03 - 优化 => 取消功能

        代码

<body>
  <input type="text" />
  <button>取消</button>
  <script>
    function starDebounce(fn, delay) {
      let timer = null;
      // 1. 返回新的函数,此时这个函数中的this为绑定的dom对象 => 相当于 inputDom的oninput指向这个函数
      // 2. 接受参数
      const _debounce = function (...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          // 3. 绑定this,同时把参数传递给fn
          fn.apply(this, args);
          timer = null;
        }, delay);
      };
      // 3. 因为函数也是一个对象,所以
      _debounce.cancle = function () {
        if (timer) clearTimeout(timer);
        timer = null;
      };
      // 4. 返回函数
      return _debounce;
    }
  </script>
  <script>
    const inputDom = document.querySelector('input');
    let counter = 1;
    const debounceFn = starDebounce(function (e) {
      console.log(`发送网络请求 :${counter++} => ${this.value} `, e);
    }, 1000);
    // 执行
    inputDom.oninput = debounceFn;

    // 取消
    const btnDom = document.querySelector('button');
    btnDom.onclick = debounceFn.cancle;
  </script>
</body>

        效果

04 - 优化 => 立即执行功能

        代码

<body>
  <input type="text" />
  <button>取消</button>
  <script>
    function starDebounce(fn, delay, immediate = false) {
      let timer = null;
      // 1. 是否是第一次执行
      let isInvoke = true;
      const _debounce = function (...args) {
        if (timer) clearTimeout(timer);

        // 2. 第一次操作不需要延迟
        if (immediate && isInvoke) {
          fn.apply(this, args);
          timer = null;
          isInvoke = false;
          return;
        }

        timer = setTimeout(() => {
          fn.apply(this, args);
          timer = null;
          // 3. 执行完后恢复
          isInvoke = true;
        }, delay);
      };
      _debounce.cancle = function () {
        if (timer) clearTimeout(timer);
        timer = null;
        // 3. 执行完后恢复
        isInvoke = true;
      };
      return _debounce;
    }
  </script>
  <script>
    const inputDom = document.querySelector('input');
    let counter = 1;
    const debounceFn = starDebounce(
      function (e) {
        console.log(`发送网络请求 :${counter++} => ${this.value} `, e);
      },
      1000,
      true
    );
    // 执行
    inputDom.oninput = debounceFn;

    // 取消
    const btnDom = document.querySelector('button');
    btnDom.onclick = debounceFn.cancle;
  </script>
</body>

        效果

05 - 优化 => 获取返回值

        代码

<body>
  <input type="text" />
  <button>取消</button>
  <script>
    function starDebounce(fn, delay, immediate = false) {
      let timer = null;
      let isInvoke = true;
      const _debounce = function (...args) {
        // 1. 因为有延迟,使用promise
        return new Promise((resolve, reject) => {
          try {
            let res = null;
            if (timer) clearTimeout(timer);
            // 2. 第一次操作不需要延迟
            if (immediate && isInvoke) {
              // 2. 接受函数的返回值
              res = fn.apply(this, args);
              // 3. 传递出去
              resolve(res);
              timer = null;
              isInvoke = false;
              return;
            }

            timer = setTimeout(() => {
              res = fn.apply(this, args);
              // 3. 传递出去
              resolve(res);
              timer = null;
              // 3. 执行完后恢复
              isInvoke = true;
            }, delay);
          } catch (error) {
            reject(error);
          }
        });
      };
      _debounce.cancle = function () {
        if (timer) clearTimeout(timer);
        timer = null;
        // 3. 执行完后恢复
        isInvoke = true;
      };
      return _debounce;
    }
  </script>
  <script>
    const debounceFn = starDebounce(function (name, text, age) {
      console.log(name, text, age);
      return '执行完了';
    }, 1000);
    // 4. 传递参数并接受返回值
    debounceFn('coder', 'star', 18).then((res) => {
      console.log(res);
    });
    // 取消
    const btnDom = document.querySelector('button');
    btnDom.onclick = debounceFn.cancle;
  </script>
</body>

06 - 最终版本

function starDebounce(fn, delay, immediate = false) {
  let timer = null;
  let isInvoke = true;

  const _debounce = function (...args) {
    return new Promise((resolve, reject) => {
      try {
        let res = null;
        if (timer) clearTimeout(timer);
        if (immediate && isInvoke) {
          res = fn.apply(this, args);
          resolve(res);
          timer = null;
          isInvoke = false;
          return;
        }

        timer = setTimeout(() => {
          res = fn.apply(this, args);
          resolve(res);
          timer = null;
          isInvoke = true;
        }, delay);
      } catch (error) {
        reject(error);
      }
    });
  };
  _debounce.cancle = function () {
    if (timer) clearTimeout(timer);
    timer = null;
    isInvoke = true;
  };
  return _debounce;
}

二、节流 throttle 

1. 概念

  • 当事件触发时,会执行这个事件的响应函数
  • 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数
  • 不管在这个中间有多少次触发这个事件,执行函数的频繁总是固定的

2. 应用场景

  • 监听页面的滚动事件
  • 鼠标移动事件
  • 用户频繁点击按钮操作
  • 轮播图的按钮滚动

3. 使用 underscore 实现节流

01 - 代码

<body>
  <input type="text" />

  <!-- 1. CDN引入: 网络的js文件 -->
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.4/underscore-umd-min.js"></script>

  <script>
    // 2.获取input元素
    const inputEl = document.querySelector('input');

    // 3.节流处理代码
    let counter = 1;
    inputEl.oninput = _.throttle(function () {
      console.log(`发送网络请求${counter++}:`, this.value);
    }, 1000);
  </script>
</body>

02 - 效果

4. 实现

01 - 基本实现

        代码

<body>
  <input type="text" />

  <script>
    /**
     * fn : 传入的函数
     * interval : 间隔的请求时间
     * 公式 : 等待时间 = 间隔时间 - ( 当前时间 - 开始时间 ) => interval - ( nowTime - startTime )
     *
     * 等待时间 <= 0 即可执行函数
     */
    function starThrottle(fn, interval) {
      // 1. 获取开始时间,赋初始值为0
      let startTime = 0;
      function throttle() {
        // 2. 获取当前时间
        const nowTime = new Date().getTime();
        // 3. 计算等待时间
        const waitTime = interval - (nowTime - startTime);
        // 4. 判断是否执行函数,第一次会默认执行
        if (waitTime <= 0) {
          fn();
          // 5.一旦执行完后,把当前时间赋值给开始时间
          startTime = nowTime;
        }
      }
      return throttle;
    }
  </script>

  <script>
    // 2.获取input元素
    const inputEl = document.querySelector('input');

    // 3.节流处理代码
    let counter = 1;
    inputEl.oninput = starThrottle(function () {
      console.log(`发送网络请求${counter++}:`, this.value);
    }, 2000);
  </script>
</body>

        效果

02 - 优化 => this 和 参数的绑定

        代码

<body>
  <input type="text" />

  <script>
    function starThrottle(fn, interval) {
      let startTime = 0;
      // 节流函数接受参数
      function throttle(...args) {
        const nowTime = new Date().getTime();
        const waitTime = interval - (nowTime - startTime);
        if (waitTime <= 0) {
          // 相当于 inputEl 直接调用该函数,所以this指向 inputEl
          // 把参数传给函数,即可在回调函数中拿到
          fn.apply(this, args);
          startTime = nowTime;
        }
      }
      return throttle;
    }
  </script>

  <script>
    // 2.获取input元素
    const inputEl = document.querySelector('input');

    // 3.节流处理代码
    let counter = 1;
    inputEl.oninput = starThrottle(function (e) {
      // 使用
      console.log(`发送网络请求${counter++}:`, this.value, e);
    }, 2000);
  </script>
</body>

        效果

03 - 优化 => 立即执行功能

        代码

<body>
  <input type="text" />

  <script>
    /**
     * fn : 传入的函数
     * interval : 间隔时间
     * immediate : 第一次是否执行,默认是马上执行的
     */
    function starThrottle(fn, interval, immediate = true) {
      let startTime = 0;
      function throttle(...args) {
        const nowTime = new Date().getTime();
        // 如果immediate为false,且 开始时间为0时
        // 把当前时间赋值给开始时间,这样使得第一次不会执行
        if (!immediate && startTime === 0) {
          startTime = nowTime;
        }
        const waitTime = interval - (nowTime - startTime);
        if (waitTime <= 0) {
          fn.apply(this, args);
          startTime = nowTime;
        }
      }
      return throttle;
    }
  </script>

  <script>
    // 2.获取input元素
    const inputEl = document.querySelector('input');

    // 3.节流处理代码
    let counter = 1;
    inputEl.oninput = starThrottle(
      function (e) {
        // 使用
        console.log(`发送网络请求${counter++}:`, this.value, e);
      },
      1000,
      false
    );
  </script>
</body>

        效果

04 - 优化 => 获取返回值

        代码

<script>
  function starThrottle(fn, interval, immediate = true) {
    let startTime = 0;
    function throttle(...args) {
      return new Promise((resolve, reject) => {
        try {
          const nowTime = new Date().getTime();
          // 如果immediate为false,且 开始时间为0时
          // 把当前时间赋值给开始时间,这样使得第一次不会执行
          if (!immediate && startTime === 0) {
            startTime = nowTime;
          }
          const waitTime = interval - (nowTime - startTime);
          if (waitTime <= 0) {
            // 
            const res = fn.apply(this, args);
            resolve(res);
            startTime = nowTime;
          }
        } catch (error) {
          reject(error);
        }
      });
    }
    return throttle;
  }
</script>

<script>
  // 3.节流处理代码
  let counter = 1;
  const throttleFn = starThrottle(function (...e) {
    // 使用
    console.log(`发送网络请求${counter++}:`, e);
    return '无敌 🦖 战神';
  }, 1000);
  throttleFn('inside', 'args').then((res) => {
    console.log('返回值:', res);
  });
</script>

05 - 最终版本

function starThrottle(fn, interval, immediate = true) {
  let startTime = 0;
  function throttle(...args) {
    return new Promise((resolve, reject) => {
      try {
        const nowTime = new Date().getTime();
        if (!immediate && startTime === 0) {
          startTime = nowTime;
        }
        const waitTime = interval - (nowTime - startTime);
        if (waitTime <= 0) {
          //
          const res = fn.apply(this, args);
          resolve(res);
          startTime = nowTime;
        }
      } catch (error) {
        reject(error);
      }
    });
  }
  return throttle;
}

5. 第二种方法实现节流

// 直接最终版本

<script>
  /**
   * fn : 传入的函数
   * interval : 间隔时间
   * 不方便设置第一次马上执行
   */
  function starThrottle(fn, interval) {
    // 1. 是否锁住
    let locked = false;
    function throttle(...args) {
      return new Promise((resolve, reject) => {
        try {
          // 2. 判断是否锁住,如果锁住直接返回
          if (locked) return true;
          // 3. 进来后直接锁住
          locked = true;
          // 4. 开启定时器
          setTimeout(() => {
            const res = fn.apply(this, args);
            resolve(res);
            // 5. 执行完后,解锁
            locked = false;
          }, interval);
        } catch (error) {
          reject(error);
        }
      });
    }
    return throttle;
  }
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值