• JS防抖和节流:原来如此简单


    一、函数防抖

      前端开发工作中,我们经常在一个事件发生后执行某个操作,比如鼠标移动时打印一些东西:

    1 window.addEventListener("mousemove", ()=>console.log(123));
    2  //测试发现鼠标移动了1毫米,回调函数执行了将近10次,这种做法是非常耗费资源的。

      这就像电梯,如果一个电梯的设计是每进去一个人就立即关门,那么如果有10个人排队进会是怎么样呢?多耗电而且很危险。

      解决方法就是每进一个人都重新倒计时N秒再关门,这样只要每个人都在前一个人进去N秒之内进门,那么每进一个人,电梯都会重新计时N秒,所以它只会在最后一个人进门N秒之后再启动关门程序。

      这里有三个关键点:「事件是高频率发生的」、「在前一个发生后N秒内发生下一个」、「重新倒计时」

      函数防抖(debounce)就是以上解决方案的JavaScript实现,上面代码的改写思路是:每次事件发生后都只做两件事——「清除旧的计时器」、「设置新的计时器」

    //重置计时器的函数
     function debounce(func, ms){
         let timer = null;
         function reTimer(){
             //重新计时
             clearTimeout(timer)
             timer = setTimeout(func, ms)
         }
         return reTimer;
     }
     ​
     //要执行的动作
     function handle(){
       console.log("--- do something ---")
     }
     ​
     //绑定事件:每次鼠标移动时,就会执行debounce返回的reTimer函数
     window.addEventListener("mousemove", debounce(handle, 1000))

     

    进一步分析

      前面说了,每次事件发生后都只做两件事——「清除旧的计时器」、「设置新的计时器」,那么为什么要在addEventListener里执行debounce函数呢?

      因为reTimer函数需要操作来自父级作用域的变量timer,而debounce函数就是为了创建这样一个作用域,使得每次执行reTimer函数时timer变量都是存在的。

      如果要求不使用debounce函数,我们就得把timer变量定义在addEventListener之前:

    //重置倒计时
     function reTimer(){
       if(timer){
         clearTimeout(timer)
       }
       timer = setTimeout(handle, 1000)
     }
     ​
     //事件处理
     function handle(){
       console.log("--- do something ---")
     }
     ​
     //事件绑定
     let timer = null; //或者 window.timer = null
     window.addEventListener("mousemove", reTimer)

      不过,相比使用debounce函数,这样做就不那么优雅了。

      addEventListener的目的是操作timer变量,而timer在debounce的作用域内,addEventListener访问不到,所以用debounce返回的reTimer去访问,这就是闭包了。

      防抖是让重复事件的处理函数只在最后一次发生时执行,而闭包只是一个更好的实现方案。

     

    二、函数节流

      理解了函数防抖,函数节流也就好办了,我们只需要理解场景和方案。

      假如我们正在做一个输入框,要求每输入一个字符都调用一个API来查询数据,从而实现联想、自动补全等功能,然而我们的输入速度是很快的,可能还没等第一个字符的查询结果出来,第二个字符就已经敲进去了,所以我们需要让查询频率小一点,具体做法就是在输入的过程中,每隔N秒才查询一次。

      这里的关键点是:「事件是高频率发生的」、「在前一个发生后N秒内发生下一个」、「一个计时结束后再重新计时」

    定时器实现节流

      节流函数(throttle)要做的就是:「确保一个计时器停止时再重新计时」 

    /*
      * 节流函数生成器
      *    传递事件处理函数和延迟时间
      *    返回节流函数
      */
     function throttleGen(fn, delay) {
       let timer = null;
       function throller() {
         if (timer === null) {
           timer = setTimeout(function () {
             fn();
             timer = null;
           }, delay)
         }
       }
     ​
       return throller;
     }
     ​
     //事件处理函数
     function handle() {
       console.log('-- do something --');
     }
     ​
     ​
     //绑定事件
     window.addEventListener("mousemove", throttleGen(handle, 1000))

     

     

    三、防抖和节流的对比

      用输入时执行动作的例子可以对比防抖和节流,防抖就是等最后一个字符输入完N秒之后再查询,而节流是在输入过程中每隔N秒查询一次。
      以上代码为了保持简单,刻意忽略了绑定上下文等操作,在实际编码过程中,只要稍加改动即可使用稳定可靠的防抖和节流函数,比如这样:

    function debounce(fn, delay) {
        let timer = null; 
        return function () {
            var _this = this;         //这里改了
            clearTimeout(timer);
            timer = setTimeout(function () {
                fn.apply(_this);     //这里改了
            }, delay);
        };
    }
  • 相关阅读:
    JS事件处理中心的构想
    form的novalidate属性
    AOP思想在JS中的应用
    推行浏览器升级提示,从自己做起
    doT.js模板引擎
    关于JS获取元素宽度的一点儿思考
    类似百度图片,360图片页面的布局插件
    ASCII、Unicode、UTF-8编码关系
    python字符串格式化符号及转移字符含义
    python字符串的方法介绍
  • 原文地址:https://www.cnblogs.com/MPK-dev/p/12142929.html
Copyright © 2020-2023  润新知