防抖和节流函数

防抖和节流函数

Posted by SkioFox on July 3, 2017

函数防抖与节流

  • 函数防抖: 函数防抖就是对于一定时间段的连续的函数调用,只让其执行一次。
  • 函数节流: 降低触发回调的频率,让一个函数不要执行得太频繁,减少一些过快的调用来节流。

    // 函数防抖:延时执行:需要间隔delay时间触发才会生效
    // 函数防抖的应用场景:输入框搜索自动补全事件,频繁操作点赞和取消点赞,滚动事件,resize窗口变化
    
    // 简单的防抖动函数
    function debounce(func, wait) {
        // 定时器变量
        var timeout;
        return function() {
            // 每次触发 scroll handler 时先清除定时器
            clearTimeout(timeout);
            // 指定 xx ms 后触发真正想进行的操作 handler
            timeout = setTimeout(func, wait);
        };
    };
      
    // 实际想绑定在 scroll 事件上的 handler
    function realFunc(){
        console.log("Success");
    }
      
    // 采用了防抖动
    window.addEventListener('scroll',debounce(realFunc,500));
    // 没采用防抖动
    window.addEventListener('scroll',realFunc);
    
    // 封装版
    /**
    * @desc 函数防抖
    * @param func 函数
    * @param wait 延迟执行毫秒数
    * @param immediate true 表立即执行,false 表非立即执行
    */
    function debounce(func,wait,immediate) {
        let timeout;
        return function () {
            // 获取 this 和 参数,是为了让 debounce 函数最终返回的函数 this 指向不变以及依旧能接受到 e 参数。
            let context = this; // 这里this指向调用debounce函数的对象
            let args = arguments;
    
            if (timeout) clearTimeout(timeout);
            if (immediate) {
                var callNow = !timeout;
                timeout = setTimeout(() => {
                    timeout = null;
                }, wait)
                if (callNow) func.apply(context, args)
            }
            else {
                timeout = setTimeout(function(){
                    func.apply(context, args)
                }, wait);
            }
        }
    }
    
    // 函数节流 throttle
    // 原理:当达到了一定的时间间隔就会执行一次;可以理解为是缩减执行频率,即每隔多少事件执行一次
    // 应用场景:如果想要每隔一段时间发送一次请求,而不是等到客户触发最后一次操作才发送请求,可以这样实现
    // 鼠标不断点击触发,mousedown(单位时间内只触发一次),监听滚动事件,比如是否滑到底部自动加载更多,搜索框,向后台发送请求
    /**
    * @desc 函数节流
    * @param func 函数
    * @param wait 延迟执行毫秒数
    * @param type 1 表时间戳版,2 表定时器版
    */
    function throttle(func, wait ,type) {
        if(type===1){
            let previous = 0;
        }else if(type===2){
            let timeout;
        }
        return function() {
            let context = this;
            let args = arguments;
            if(type===1){
                let now = Date.now();
    
                if (now - previous > wait) {
                    func.apply(context, args);
                    previous = now;
                }
            }else if(type===2){
                if (!timeout) {
                    timeout = setTimeout(() => {
                        timeout = null;
                        func.apply(context, args)
                    }, wait)
                }
            }
        }
    }
    

    tips: 普通函数 return this, this的指向取决于调用函数的对象。在setInterval和setTimeout中传入函数时,函数中的this会指向window对象。

      function LateBloomer() {
        this.petalCount = Math.ceil(Math.random() * 12) + 1;
      }
    
      // Declare bloom after a delay of 2 second
      LateBloomer.prototype.bloom = function() {
        // 这个写法会报 I am a beautiful flower with undefined petals!
        // 原因:在setInterval和setTimeout中传入函数时,函数中的this会指向window对象
        window.setTimeout(this.declare, 2000);
        // 如果写成 window.setTimeout(this.declare(), 2000); 会立即执行,就没有延迟效果了。
      };
    
      LateBloomer.prototype.declare = function() {
        console.log('I am a beautiful flower with ' +
          this.petalCount + ' petals!');
      };
    
      var flower = new LateBloomer();
      flower.bloom();  // 二秒钟后, 调用'declare'方法
    

    解决办法:

    1. 将bind换成call,apply也会导致立即执行,延迟效果会失效window.setTimeout(this.declare.bind(this), 2000);
    2. 使用es6中的箭头函数,因为在箭头函数中this是固定的。箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。window.setTimeout(() => this.declare(), 2000);
    • setTimeout this指向
    // 函数作为方法调用还是构造函数调用,this是不同的
      function Foo() {
        this.value = 42;
        this.method = function() {
            // this 指向全局对象
            alert(this)   // 输出window
            alert(this.value); // 输出:undefined
        };
        setTimeout(this.method, 500);  // this指向Foo的实例对象
      }
      new Foo();
    
    • setTimeout中的延迟执行代码中的this永远都指向window

    • setTimeout(this.method, time)这种形式中的this,即上文中提到的第一个this,是根据上下文来判断的,默认为全局作用域,但不一定总是处于全局下,具体问题具体分析。

    • setTimeout(匿名函数, time)这种形式下,匿名函数中的变量也需要根据上下文来判断,具体问题具体分析。在这里匿名函数的使用形成了一个闭包,从而能访问到外层函数的局部变量。

    • 可以理解setTimeout函数类似挂起的函数或者事件,当延时时间到,主进程才会将setTimeout函数在widows作用域下执行(JS执行过程可以简单理解为一个进程+一个任务队列,队列中是等待被执行的延时函数或者事件回调的函数等等)。