函数节流和防抖

函数的高阶使用2

咱们来看一个分析:
若是要实现一个拖拽功能,须要一路监听 mousemove 事件,在回调中获取元素当前位置,而后重置dom的位置来进行样式改变。若是不加以控制,每移动必定像素而触发的回调数量很是惊人,回调中又伴随着 DOM 操做,继而引起浏览器的重排与重绘,性能差的浏览器可能就会直接假死.html

在某些状况下会引起函数被很是频繁地调用,而形成大的性能问题。解决性能问题的处理办法就是函数节流和函数防抖。前端

函数防抖

是函数在特定的时间内不被再调用后执行。 也就是让某个函数在上一次执行后,知足等待某个时间内再也不触发此函数后再执行,而在这个等待时间内再次触发此函数,等待时间会从新计算。ajax

应用场景

  1. 用户注册时候手机号码验证和邮箱验证只有等到用户输入完毕后,前端才须要检查格式是否正确,若是不正确,再弹出提示语。
  2. scroll/resize事件
  3. 文本连续输入,ajax验证/关键字搜索'

实现方式

  • 函数防抖的要点,也是须要一个setTimeout来辅助实现。延迟执行须要跑的代码。
  • 若是方法屡次触发,则把上次记录的延迟执行代码用clearTimeout清掉,从新开始。
  • 若是计时完毕,没有方法进来访问触发,则执行代码。
    举个例子: 在移动鼠标时触发打印函数 加上函数防抖。
var timer = false;
document.querySelector("#div").mousemove = function(){
    clearTimeout(timer);  // 当事件触发的时候,清除以前等待执行的函数,
    timer = setTimeout(function(){ //  开启新的延时执行函数
        console.log("函数防抖");
    }, 300);
};

咱们如今对上述方法封装一下浏览器

// 函数防抖
const debounce = function(fn, wait=300){
    return function(){
        clearTimeout(fn.timer);  // 当事件触发的时候,清除以前等待执行的函数,
        fn.timer = setTimeout(fn, 300);
    }
}

可是咱们就会发现 fn 的this 指向发生了改变, fn参数是接收不到的。app

function debounce = function(fn, wait=300){
    return function(){
        clearTimeout(fn.timer);  // 当事件触发的时候,清除以前等待执行的函数,
        fn.timer = setTimeout(fn.bind(this), wait);
    }
}

或者使用applydom

function debounce = function(fn, wait=100){
    return function(){
        clearTimeout(fn.timer);  // 当事件触发的时候,清除以前等待执行的函数,
        fn.timer = setTimeout( ()=>{ // 不能写匿名函数,this会发生改变
            return fn.apply(this, arguments); 
        }, wait);
    }
}

函数节流

函数节流,即限制函数的执行频率,在持续触发事件的状况下,间断地执行函数。只要当前函数没有执行完成,任何新触发的函数都会被忽略。就是在固定的时间间隔内函数只会被调用一次。异步

适用场景

  1. 频繁的mousemove/keydown,好比高频的鼠标移动,游戏射击类的
  2. 搜索联想: 监听keypress事件,而后异步去查询结果. 若是快速输入过多字符串, 就会触发高频请求。
  3. 监听滚动事件判断是否到页面底部自动加载更多(scroll事件)

实现简易

  • 函数节流的要点是,声明一个变量当标志位,记录当前代码是否在执行。
  • 若是空闲,则能够正常触发方法执行。
  • 若是代码正在执行,则取消此次方法执行,直接return。
var timer = false;
document.querySelector("#div").mousemove = function(){
    if(timer)
        return;
    timer = setTimeout(function(){   //  正在执行
        console.log("函数节流");
        timer = false; // 表示不在执行
    }, 100);
};

在来改写一下:函数

const throttle = function(fn, wait=100){
    return function(){
        if(fn.timer){ return; }
        fn.timer = setTimeout(()=>{
            fn.apply(this,arguments);
            fn.timer = false;
        },wait)
    }
}

总结

函数的节流和函数的去抖都是经过减小实际逻辑处理过程的执行来提升事件处理函数运行性能的手段,并无实质上减小事件的触发次数。
某些函数确实是用户主动调用的,但由于一些客观的缘由,这些函数会严重地影响页面性能。解决方案:例如分页技术和延迟加载,均可以免在页面上同时加载过多数据,形成的页面卡顿。甚至假死现象。其实,优化就是合理利用性能。性能

underscore v1.7.0相关的源码实现

_.debounce = function(func, wait, immediate) {
    // immediate默认为false
    var timeout, args, context, timestamp, result;

    var later = function() {
      // 当wait指定的时间间隔期间屡次调用_.debounce返回的函数,则会不断更新timestamp的值,致使last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func
      var last = _.now() - timestamp;

      if (last < wait && last >= 0) {
        timeout = setTimeout(later, wait - last);
      } else {
        timeout = null;
        if (!immediate) {
          result = func.apply(context, args);
          if (!timeout) context = args = null;
        }
      }
    };

    return function() {
      context = this;
      args = arguments;
      timestamp = _.now();
      // 第一次调用该方法时,且immediate为true,则调用func函数
      var callNow = immediate && !timeout;
      // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数
      if (!timeout) timeout = setTimeout(later, wait);
      if (callNow) {
        result = func.apply(context, args);
        context = args = null;
      }

      return result;
    };
  };
_.throttle = function(func, wait, options) {
    /* options的默认值
     *  表示首次调用返回值方法时,会立刻调用func;不然仅会记录当前时刻,当第二次调用的时间间隔超过wait时,才调用func。
     *  options.leading = true;
     * 表示当调用方法时,未到达wait指定的时间间隔,则启动计时器延迟调用func函数,若后续在既未达到wait指定的时间间隔和func函数又未被调用的状况下调用返回值方法,则被调用请求将被丢弃。
     *  options.trailing = true; 
     * 注意:当options.trailing = false时,效果与上面的简单实现效果相同
     */
    var context, args, result;
    var timeout = null;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      // 计算剩余时间
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      // 当到达wait指定的时间间隔,则调用func函数
      // 精彩之处:按理来讲remaining <= 0已经足够证实已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则立刻执行func函数。
      if (remaining <= 0 || remaining > wait) {
        // 因为setTimeout存在最小时间精度问题,所以会存在到达wait的时间间隔,但以前设置的setTimeout操做还没被执行,所以为保险起见,这里先清理setTimeout操做
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // options.trailing=true时,延时执行func函数
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

参考[http://www.cnblogs.com/fsjohnhuang/p/4147810.html]优化

相关文章
相关标签/搜索