防抖节流

2022-10-09  本文已影响0人  未路过

22. 防抖节流

防抖和节流的概念其实最早并不是出现在软件工程中,防抖是出现在电子元件中,节流出现在流体流动中

而JavaScript是事件驱动的,大量的操作会触发事件,加入到事件队列中处理。

而对于某些频繁的事件处理会造成性能的损耗,我们就可以通过防抖和节流来限制事件频繁的发生;

1.防抖

175.jpg

只有等待了一段时间再也没有事件触发之后,才会真正的执行响应函数

2. 防抖函数的应用场景

176.jpg

3. 节流

177.jpg

在一定时间之内,无论触发了多少次操作,执行函数的频率总是固定的

4. 节流函数的应用场景

178.jpg

5. 防抖案例

179.jpg

6.Underscore

180.jpg 181.jpg

underscore比较轻量级

debance

去抖动; 防反跳; 防抖动; 弹跳; 抖动消除;

throttle

n.节流阀; 节流杆; 风门; 风门杆;

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text">
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.2/underscore-umd-min.js"></script>
  <script>
    const inputEl = document.querySelector("input")
    let counter = 0;
    const inputChange = function() {
      console.log(`发送了第${++counter}次网络请求`);
    }
 //不进行防抖节流处理
 // inputEl.oninput = inputChange;

 //防抖处理
 //inputEl.oninput = _.debounce(inputChange, 2000)

 //节流处理
 inputEl.oninput= _.throttle(inputChange, 2000)
  </script>
</body>

</html>

7.自定义防抖和节流函数

182.jpg

1.基本实现

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      height: 100px;
      width: 100px;
    }
  </style>
</head>

<body>
  <input type="text" id="myinput">
  <div id="myDiv"></div>
  <script>
  
    const input = document.querySelector("#myinput");
    const mydiv = document.querySelector("#myDiv");

    let counter = 0;
    input.oninput = debounce(myFunc, 1000);
   
    function myFunc() {
      console.log(`发送了第${++counter}次网络请求`);
    //   mydiv.innerHTML = this.value;//发送请求
    }

  
    //在1s之内有没有被执行,被执行的话,再延迟1s,没有的话,就执行
    //主要就是演出多久执行这个函数

    function debounce(fn, delay) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null;

      // 2.真正执行的函数
      function _debounce(){

        // 取消上一次的定时器
        if(timer) {
       clearTimeout(timer)
     }

     // 延迟执行
      timer = setTimeout(() => {
        // 外部传入的真正要执行的函数
        fn()
      }, delay)
      }
      return _debounce
    }
   
  </script>
</body>

</html>

2. 优化参数和this指向

有两个问题

1执行这个函数的this指向的不是input这个dom元素

2是返回的这个函数应该可以传入参数

_debounce这个函数的this应该指向的是input这个元素

因为相当于input.oninput = _debounce,

执行fn的时候,因为是独立调用,所以fn这个函数的this指向的是window,我们得使用apply等方法给他绑定this。

如果这里的settiemout是正常函数,可以通过bind绑定

        // 延迟执行
        timer = setTimeout(function () {
          fn.apply(this)
        }.bind(this), delay)
      }

如果使用箭头函数的话,箭头函数不绑定this,就会找外层作用域的this

    // 延迟执行
        timer = setTimeout(() => {
          fn.apply(this)
        }, delay)
      }

然后,关于event事件,

  input.oninput = myFunc;

    function myFunc(e) {
      console.log(e);
      console.log(`发送了第${++counter}次网络请求`);
      //   mydiv.innerHTML = this.value;//发送请求
    }

fn是可以传入参数的,这个时候,就需要接受参数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      height: 100px;
      width: 100px;
    }
  </style>
</head>

<body>
  <input type="text" id="myinput">
  <div id="myDiv"></div>
  <script>

    const input = document.querySelector("#myinput");
    const mydiv = document.querySelector("#myDiv");

    let counter = 0;
    input.oninput = debounce(myFunc, 1000);


    function myFunc(e) {
      console.log(e);
      console.log(`发送了第${++counter}次网络请求`);
      //   mydiv.innerHTML = this.value;//发送请求
    }


    //在1s之内有没有被执行,被执行的话,再延迟1s,没有的话,就执行
    //主要就是演出多久执行这个函数

    function debounce(fn, delay) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null;

      // 2.真正执行的函数
      function _debounce(...arg) {

        // 取消上一次的定时器
        if (timer) {
          clearTimeout(timer)
        }
          
        // 延迟执行
        timer = setTimeout(() => {
          fn.apply(this, arg)
        }, delay)
      }
      return _debounce
    }

  </script>
</body>

</html>

3.优化立即执行效果(第一次立即执行)

我们希望发送第一次的时候立即发送网络请求,其他后面的该延迟的就延迟。

比如用户一直连着输入,这时候timout一直取消,不会发送任何网络请求,这样不好,就是用户按下第一个以后就马上发第一个,展示点信息,不然用户以为搜索框不起作用。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      height: 100px;
      width: 100px;
    }
  </style>
</head>

<body>
  <input type="text" id="myinput">
  <div id="myDiv"></div>
  <script>

    const input = document.querySelector("#myinput");
    const mydiv = document.querySelector("#myDiv");

    let counter = 0;
    input.oninput = debounce(myFunc, 1000, true);


    function myFunc(e) {
      console.log(e);
      console.log(`发送了第${++counter}次网络请求`);
      //   mydiv.innerHTML = this.value;//发送请求
    }


    //在1s之内有没有被执行,被执行的话,再延迟1s,没有的话,就执行
    //主要就是演出多久执行这个函数

    function debounce(fn, delay, immediate = false) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null;
      let first = true;

      // 2.真正执行的函数
      function _debounce(...arg) {
        if( first && immediate) {
          fn.apply(this, arg);
          first = false
        }

        // 取消上一次的定时器
        if (timer) {
          clearTimeout(timer)
        }

        // 延迟执行
        timer = setTimeout(() => {
          fn.apply(this, arg);
          first = true;
        }, delay)
      }
      return _debounce
    }

  </script>
</body>

</html>

4. 优化取消操作(增加取消功能)

如果有一个按键,在输入完1s之内,它按了取消按钮,这个请求不发送

函数对象上增加方法。

搜索框后面有时候有一个取消键,就是我们设置的timeout时间很长,用户按下取消键的时候,我们可以取消发送网络请求。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      height: 100px;
      width: 100px;
    }
  </style>
</head>

<body>
  <input type="text" id="myinput">
  <div id="myDiv"></div>
  <button>取消</button>
  <script>

    const input = document.querySelector("#myinput");
    const mydiv = document.querySelector("#myDiv");
    const myButton = document.querySelector("button")
    let counter = 0;
    const debounceChange = debounce(myFunc, 2000);
    input.oninput = debounceChange;
    myButton.onclick = function () {
      debounceChange.cancle()
    }


    function myFunc(e) {
      console.log(e);
      console.log(`发送了第${++counter}次网络请求`);
      //   mydiv.innerHTML = this.value;//发送请求
    }


    //在1s之内有没有被执行,被执行的话,再延迟1s,没有的话,就执行
    //主要就是延迟多久执行这个函数

    function debounce(fn, delay, immediate = false) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null;
      let first = true;

      // 2.真正执行的函数
      function _debounce(...arg) {
        if (first && immediate) {
          fn.apply(this, arg);
          first = false
        }

        // 取消上一次的定时器
        if (timer) {
          clearTimeout(timer)
        }

        // 延迟执行
        timer = setTimeout(() => {
          fn.apply(this, arg);
          first = true;
          timer = null;
        }, delay)
      }

      //封装取消功能 
      //函数本身也是对象,可以给函数添加属性
      _debounce.cancle = function () {
        if (timer) {
          clearTimeout(timer);
          timer = null;
          first = true;
        }
      }

      return _debounce
    }



  </script>
</body>

</html>

5.优化返回值

如果发送网络请求的响应函数有返回值呢?就是真正执行的myFunc如果有返回值的化,

有两种方法,1种是给函数在添加一个参数,callback

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      height: 100px;
      width: 100px;
    }
  </style>
</head>

<body>
  <input type="text" id="myinput">
  <div id="myDiv"></div>
  <button>取消</button>
  <script>

    const input = document.querySelector("#myinput");
    const mydiv = document.querySelector("#myDiv");
    const myButton = document.querySelector("button")
    let counter = 0;
    const debounceChange = debounce(myFunc, 2000, false, (res) => {
      console.log("我是返回值" + res);
    });
    input.oninput = debounceChange;
    myButton.onclick = function () {
      debounceChange.cancle()
    }


    function myFunc(e) {
      console.log(e);
      console.log(`发送了第${++counter}次网络请求`);
      //   mydiv.innerHTML = this.value;//发送请求
      return "abc"
    }


    //在1s之内有没有被执行,被执行的话,再延迟1s,没有的话,就执行
    //主要就是延迟多久执行这个函数

    function debounce(fn, delay, immediate = false, resultCallback) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null;
      let first = true;

      // 2.真正执行的函数
      function _debounce(...arg) {
        if (first && immediate) {
          const result = fn.apply(this, arg);
            first = false
            
          if (resultCallback && typeof resultCallback === "function") 
              resultCallback(result)
        }

        // 取消上一次的定时器
        if (timer) {
          clearTimeout(timer)
        }

        // 延迟执行
        timer = setTimeout(() => {
          const result = fn.apply(this, arg);
              first = true;
              timer = null;
          if (resultCallback && typeof resultCallback === "function")        
              resultCallback(result)
              
        }, delay)
      }

      //封装取消功能 
      //函数本身也是对象,可以给函数添加属性
      _debounce.cancle = function () {
        if (timer) {
          clearTimeout(timer);
          timer = null;
          first = true;
        }
      }

      return _debounce
    }


  </script>
</body>

</html>

第二种方法就是使用promise

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      height: 100px;
      width: 100px;
    }
  </style>
</head>

<body>
  <input type="text" id="myinput">
  <div id="myDiv"></div>
  <button>取消</button>
  <script>

    const input = document.querySelector("#myinput");
    const mydiv = document.querySelector("#myDiv");
    const myButton = document.querySelector("button")
    let counter = 0;
    const debounceChange = debounce(myFunc, 2000, false);
/*     const tempCallback = () => {
      console.log(this);//window
     
    } */

    const tempCallback = function(e){
      console.log(this);
      debounceChange.apply(this, [e]).then((res) => {
        console.log("Promise的返回值结果",res);
      })

    }
    input.oninput = tempCallback;

    myButton.onclick = function () {
      debounceChange.cancle()
    }


    function myFunc(e) {
      console.log(e);
      console.log(this);
      console.log(`发送了第${++counter}次网络请求`);
      //   mydiv.innerHTML = this.value;//发送请求
      return "abc"
    }


    //在1s之内有没有被执行,被执行的话,再延迟1s,没有的话,就执行
    //主要就是延迟多久执行这个函数

    function debounce(fn, delay, immediate = false) {
    
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null;
      let first = true;

      // 2.真正执行的函数
      const  _debounce = function(...arg) {
        console.log("_debounce this :", this);
        return new Promise((resolve, reject) => {
          if (first && immediate) {
          const result = fn.apply(this, arg);
          resolve(result)
          first = false
        }

        // 取消上一次的定时器
        if (timer) {
          clearTimeout(timer)
        }

        // 延迟执行
        timer = setTimeout(() => {
          const result = fn.apply(this, arg);
          resolve(result)
          first = true;
          timer = null;
        }, delay)
        })
      }

      //封装取消功能 
      //函数本身也是对象,可以给函数添加属性
      _debounce.cancle = function () {
        if (timer) {
          clearTimeout(timer);
          timer = null;
          first = true;
        }
      }

      return _debounce
    }



  </script>
</body>

</html>

8. 自定义节流函数

在一定时间之内,无论触发了多少次操作,执行函数的频率总是固定的。

比如每10s执行一次,但是abcd输入完以后还没到10s。

先实现abcd输入完是第7秒,但是不执行,只有到第10s再执行。

这个时候距离最后一次输入距离3s之后再执行函数。如果输入完d是第10s,那么就马上执行。我们就就可以达到下面的公式。remaintime是10-(7-0)= 3,10-(10-0)=0

大于0的化就代表要等多久执行,小于0 或者等于0,就代表马上执行。

const remainTime = interval - (nowTime - startTime)

interval来自于传递来的函数,throttle函数。throttle(fn, interval)

nowTime = newDate().getTime()就是当前时间。我这次input的时候的这个当前时间。

first就是第一次是0,再第一次触发节流,发送网络事件以后,first就变成了interval。

[图片上传失败...(image-44b898-1665312315624)]

1. 基本实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      div {
        height: 100px;
        width: 100px;
      }
    </style>
  </head>

  <body>
    <input type="text" id="myinput" />
    <div id="myDiv"></div>
    <button>取消</button>
   <!--  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.2/underscore-umd-min.js"></script> -->
    <script>
      const input = document.querySelector("#myinput");
      let counter = 0;

      function myFunc() {
        console.log(`发送了第${++counter}次网络请求`);
      }

      // input.oninput = _.throttle(myFunc, 10000);
      //输入第一个的时候马上执行,然后没到interval时间之前等待执行。
      //getTime()返回距 1970 年 1 月 1 日之间的毫秒数:
      input.oninput = throttle(myFunc, 3000);

      function throttle(fn, interval) {
        //1.记录上一次执行函数的时间,也就是开始时间
        let lastTime = 0;
        const _throttle = function () {
          //2.事件触发的时候真正执行的函数

          //2.1获取当前事件触发时的时间
          const nowTime = new Date().getTime();

          //2.2 使用当前触发时间和时间间隔和上一次开始的时间,计算出还剩余多长时间需要去触发函数
          const remainTime = interval - (nowTime - lastTime);
          if (remainTime <= 0) {
            //2.3 真正出发函数
            fn();
            //2.4保留上次触发的时间
            lastTime = nowTime;
          }
        };
        return _throttle;
      }
    </script>
  </body>
</html>

再这个函数里。第一次输入框的时候,函数是立刻执行的 ,因为第一次的lasttime是0,也就是gettime是一个很大的值,那个remainTime = 3000 - (很大的值 - 0);一定是负数,所以一定会执行。

但是还有一个很大的bug就是如果最后一次输入的时间没有达到interval的时间,这个时候函数是不执行的。

就是我用3s输入完了,但是interval是10s,我等了7s,函数还是没有执行。

总结:

我们可以添加3个功能

第一个是第一次输入内容的时候触发不,leading

第二个参数是最后一次触发不触发,就是刚才的哪个bug

第三次是添加个一个callback,就是执行完函数之后,函数有返回值的情况。

我们把这些参数都放到options这个对象里面。

2. 优化立即执行效果

 if (lastTime === 0 && leading === false) {
            lastTime = nowTime;
          }

html

这个第一次是第一次在框里面输入东西。如果间隔10s,第一下输入东西

,函数执行。然后第11s输入东西,函数马上执行,这个就不输入与这个第一次的范畴了。而是本身的基本实现里面就有。因为remainTime小于0 了。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style></style>
  </head>

  <body>
    <input type="text" id="myinput" />
    <script>
      const input = document.querySelector("#myinput");
      let counter = 0;

      function myFunc() {
        console.log(`发送了第${++counter}次网络请求`);
      }

      input.oninput = throttle(myFunc, 2000, { leading: false });

      function throttle(
        fn,
        interval,
        options = {
          leading: true,
          trading: true,
        }
      ) {
        const { leading, trading } = options;
        //1.记录上一次执行函数的时间,也就是开始时间
        let lastTime = 0;
        const _throttle = function () {
          //2.事件触发的时候真正执行的函数

          //2.1获取当前事件触发时的时间
          const nowTime = new Date().getTime();

          //只有第一次的时候才会执行,因为只有第一次lastTime是0,才能造成很大的负数。
          //不想第一次触发的化,就让第一次在输入框输入内容时候的time为当前时间,而不是为0
          if (lastTime === 0 && leading === false) {
            lastTime = nowTime;
          }

          //2.2 使用当前触发时间和时间间隔和上一次开始的时间,计算出还剩余多长时间需要去触发函数
          const remainTime = interval - (nowTime - lastTime);
          if (remainTime <= 0) {
            //2.3 真正出发函数
            fn();
            //2.4保留上次触发的时间
            lastTime = nowTime;
          }
        };
        return _throttle;
      }
    </script>
  </body>
</html>

3.优化节流最后一次也可以执行

比如10s的interval,第3s的时候输入完了,需要等7s后执行。

这个时候就是设置一个timeout,时间设置为7

setTimeout(fn(), 7000)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style></style>
  </head>

  <body>
    <input type="text" id="myinput" />
    <script>
      const input = document.querySelector("#myinput");
      let counter = 0;

      function myFunc() {
        console.log(`发送了第${++counter}次网络请求`);
      }

      input.oninput = throttle(myFunc, 3000, { leading: true, trading: true });

      function throttle(
        fn,
        interval,
        options = {
          leading: false,
          trading: true,
        }
      ) {
        const { leading, trading } = options;

        //1.记录上一次执行函数的时间,也就是开始时间
        let lastTime = 0;
        let timer = null;
        const _throttle = function () {
          //2.事件触发的时候真正执行的函数

          //2.1获取当前事件触发时的时间
          const nowTime = new Date().getTime();

          //只有第一次的时候才会执行,因为只有第一次lastTime是0,才能造成很大的负数。
          //不想第一次触发的化,就让第一次在输入框输入内容时候的time为当前时间,而不是为0
          //不想第一次执行就设置下面
          if (lastTime === 0 && leading === false) {
            lastTime = nowTime;
          }

          //2.2 使用当前触发时间和时间间隔和上一次开始的时间,计算出还剩余多长时间需要去触发函数
          const remainTime = interval - (nowTime - lastTime);

          if (remainTime <= 0) {
            if (timer) {
              clearTimeout(timer);
              timer = null;
            }
            //2.3 真正出发函数
            fn();
            //2.4保留上次触发的时间
            lastTime = nowTime;
            return;
          }
          //我们只需要一个定时器就可以了,让它记住要给我们执行fn。如果再intrval之前再也没有在输入框输入内容的化。

          if (trading && !timer) {
            timer = setTimeout(() => {
              fn();
              timer = null;
              //这里不能直接使用nowTime,因为我们需要的是执行settimeout函数的时候的时间,而不是设置settimeout的时间。
              //  lastTime = nowTime;
              lastTime = new Date().getTime();
            }, remainTime);
          }
        };
        return _throttle;
      }
    </script>
  </body>
</html>

4. 优化参数和this指向

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style></style>
  </head>

  <body>
    <input type="text" id="myinput" />
    <script>
      const input = document.querySelector("#myinput");
      let counter = 0;

      function myFunc(event) {
        console.log(`发送了第${++counter}次网络请求`);
        console.log(this);
        console.log(event.target.value);
      }

      input.oninput = throttle(myFunc, 3000, { leading: true, trading: true });

      function throttle(
        fn,
        interval,
        options = {
          leading: false,
          trading: true,
        }
      ) {
        const { leading, trading } = options;

        let lastTime = 0;
        let timer = null;
        const _throttle = function (...args) {
          const nowTime = new Date().getTime();

          if (lastTime === 0 && leading === false) {
            lastTime = nowTime;
          }

          const remainTime = interval - (nowTime - lastTime);

          if (remainTime <= 0) {
            if (timer) {
              clearTimeout(timer);
              timer = null;
            }
            fn.apply(this, args);
            lastTime = nowTime;
            return;
          }

          if (trading && !timer) {
            timer = setTimeout(() => {
              fn.apply(this, args);
              timer = null;
              lastTime = new Date().getTime();
            }, remainTime);
          }
        };
        return _throttle;
      }
    </script>
  </body>
</html>

5.优化添加取消功能

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style></style>
  </head>

  <body>
    <input type="text" id="myinput" />
    <button id="btn">取消</button>
    <script>
      const input = document.querySelector("#myinput");
      const btn = document.querySelector("#btn");
      let counter = 0;

      function myFunc(event) {
        console.log(`发送了第${++counter}次网络请求`);
        console.log(this);
        console.log(event.target.value);
      }
      const _throttle = throttle(myFunc, 3000, {
        leading: true,
        trading: true,
      });
      input.oninput = _throttle;

      btn.onclick = function () {
        console.log("取消发送请求");
        _throttle.cancle();
      };

      function throttle(
        fn,
        interval,
        options = {
          leading: false,
          trading: true,
        }
      ) {
        const { leading, trading } = options;

        let lastTime = 0;
        let timer = null;
        const _throttle = function (...args) {
          const nowTime = new Date().getTime();

          if (lastTime === 0 && leading === false) {
            lastTime = nowTime;
          }

          const remainTime = interval - (nowTime - lastTime);

          if (remainTime <= 0) {
            if (timer) {
              clearTimeout(timer);
              timer = null;
            }
            fn.apply(this, args);
            lastTime = nowTime;
            return;
          }

          if (trading && !timer) {
            timer = setTimeout(() => {
              fn.apply(this, args);
              timer = null;
              lastTime = new Date().getTime();
            }, remainTime);
          }
        };
        _throttle.cancle = function () {
          if (timer) {
            clearTimeout(timer);
          }
          timer = null;
          lastTime = 0;
        };
        return _throttle;
      }
    </script>
  </body>
</html>

5.优化返回值问题

1.使用callback函数

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style></style>
  </head>

  <body>
    <input type="text" id="myinput" />
    <button id="btn">取消</button>
    <script>
      const input = document.querySelector("#myinput");
      const btn = document.querySelector("#btn");
      let counter = 0;

      function myFunc(event) {
        console.log(`发送了第${++counter}次网络请求`);
        console.log(this);
        console.log(event.target.value);
        return "i am return value";
      }
      const _throttle = throttle(myFunc, 3000, {
        leading: true,
        trading: true,
        resultCallback: (res) => {
          console.log("resultCallback:", res);
        },
      });
      input.oninput = _throttle;

      btn.onclick = function () {
        console.log("取消发送请求");
        _throttle.cancle();
      };

      function throttle(
        fn,
        interval,
        options = {
          leading: false,
          trading: true,
        }
      ) {
        const { leading, trading, resultCallback } = options;
        //如果传来的options里面没有resultCallback的化,这时候解构出来的resultCallback就是undefined

        let lastTime = 0;
        let timer = null;
        const _throttle = function (...args) {
          const nowTime = new Date().getTime();

          if (lastTime === 0 && leading === false) {
            lastTime = nowTime;
          }

          const remainTime = interval - (nowTime - lastTime);

          if (remainTime <= 0) {
            if (timer) {
              clearTimeout(timer);
              timer = null;
            }
            const result = fn.apply(this, args);
            if (resultCallback && typeof resultCallback === "function") {
              resultCallback(result);
            }
            lastTime = nowTime;
            return;
          }

          if (trading && !timer) {
            timer = setTimeout(() => {
              const result = fn.apply(this, args);
              if (resultCallback && typeof resultCallback === "function") {
                resultCallback(result);
              }

              timer = null;
              lastTime = new Date().getTime();
            }, remainTime);
          }
        };
        _throttle.cancle = function () {
          if (timer) {
            clearTimeout(timer);
          }
          timer = null;
          lastTime = 0;
        };
        return _throttle;
      }
    </script>
  </body>
</html>

2.使用promise

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style></style>
  </head>

  <body>
    <input type="text" id="myinput" />
    <button id="btn">取消</button>
    <script>
      const input = document.querySelector("#myinput");
      const btn = document.querySelector("#btn");
      let counter = 0;

      function myFunc(event) {
        console.log(`发送了第${++counter}次网络请求`);
        console.log(this);
        console.log(event.target.value);
        return "i am return value";
      }
      const _throttle = throttle(myFunc, 3000, {
        leading: true,
        trading: true,
        resultCallback: (res) => {
          console.log("resultCallback:", res);
        },
      });
      const tempCallback = function (...args) {
        console.log(this); //input这个元素
        _throttle.apply(this, args).then((res) => {
          console.log(res);
        });
      };
      input.oninput = tempCallback;

      btn.onclick = function () {
        console.log("取消发送请求");
        _throttle.apply(this).cancle();
      };

      function throttle(
        fn,
        interval,
        options = {
          leading: false,
          trading: true,
        }
      ) {
        const { leading, trading, resultCallback } = options;
        //如果传来的options里面没有resultCallback的化,这时候解构出来的resultCallback就是undefined

        let lastTime = 0;
        let timer = null;
        const _throttle = function (...args) {
          return new Promise((resolve, reject) => {
            const nowTime = new Date().getTime();

            if (lastTime === 0 && leading === false) {
              lastTime = nowTime;
            }

            const remainTime = interval - (nowTime - lastTime);

            if (remainTime <= 0) {
              if (timer) {
                clearTimeout(timer);
                timer = null;
              }
              const result = fn.apply(this, args);
              resolve(result);
              lastTime = nowTime;
              return;
            }

            if (trading && !timer) {
              timer = setTimeout(() => {
                const result = fn.apply(this, args);
                resolve(result);

                timer = null;
                lastTime = new Date().getTime();
              }, remainTime);
            }
          });
        };
        _throttle.cancle = function () {
          if (timer) {
            clearTimeout(timer);
          }
          timer = null;
          lastTime = 0;
        };
        return _throttle;
      }
    </script>
  </body>
</html>

上一篇下一篇

猜你喜欢

热点阅读