• 解析underscore中的throttle


    什么是throttle(节流)

    Throttling enforces a maximum number of times a function can be called over time.

    简单来说就是你假设给定一个wait表示这在个时间内该函数最多可以被执行一次。我们知道知道浏览器scroll触发事件的频率非常高,如果不使用节流的话,我们轻轻一滚动鼠标滑轮可能就触发了10来次某个添加到scroll事件的函数。但如果我们使用节流这个技术的话,我们设置wait为1000(ms),当我们不停地滚动滑轮10s,函数最多被执行10次。10000 / 1000 = 10

    最简单的节流

    var throttle = function(func, wait){
        var previous = 0;
        return function(){
            var now = +new Date();
            if (now - previuos > wait){
                func.apply(this, arguments);
                last = now;
            }
        }
    }
    

    这个函数利用闭包返回一个函数,而且它有两个重要的特点:

    1. 当两次函数触发的时间间隔大于wait时,func才会被调用
    2. 第一次触发时func会被调用

    underscore中的throttle

      // Returns a function, that, when invoked, will only be triggered at most once
      // during a given window of time. Normally, the throttled function will run
      // as much as it can, without ever going more than once per `wait` duration;
      // but if you'd like to disable the execution on the leading edge, pass
      // `{leading: false}`. To disable execution on the trailing edge, ditto.
      _.throttle = function(func, wait, options) {
        var timeout, context, args, result;
        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;
        };
    
        var throttled = function() {
          var now = _.now();
          if (!previous && options.leading === false) previous = now;
          var remaining = wait - (now - previous);
          context = this;
          args = arguments;
    
          if (remaining <= 0 || remaining > wait) {
            if (timeout) {
              clearTimeout(timeout);
              timeout = null;
            }
            previous = now;
            result = func.apply(context, args);
            if (!timeout) context = args = null;
          } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
          }
          return result;
        };
    
        throttled.cancel = function() {
          clearTimeout(timeout);
          previous = 0;
          timeout = context = args = null;
        };
    
        return throttled;
      };
    

    咋一看这个函数的实现比当初那个简单的函数长了很多, 别怕因为他们的思想是一模一样的,多余的代码只是为了一些额外的特性,并不复杂。

    首先多了一个options参数,它是一个对象,可以设置leadingtrailing属性。leading是提前领先的意思,在那个简单的版本中我们知道函数在第一次触发时候func是会被触发的,这就是leading。所以当我们没有设置{leading: false}时候,func会在第一次函数触发时候马上被执行。但是当我们显性地传入{leading: false}时候,func就不会马上执行。这是因为if (!previous && options.leading === false) previous = now; 开始previous为0那么条件均为真,previous = nownow - previous > wait不成立。
    即第一次触发函数会进入到

    else if (!timeout && options.trailing !== false) {
        // var remaining = wait - (now - previous);
        // now = previous;因此later会在wait毫秒后被执行
        timeout = setTimeout(later, remaining);
    }
    

    再来看看later

    var later = function() {
        previous = options.leading === false ? 0 : _.now();
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
    };
    其实写成这样更号理解
    var later = function() {
        previous = options.leading === false ? 0 : _.now();
        // 为了让将previous设为0,是让if (!previous && options.leading === false)再次成立
        // 意思就是当超过wait的时间没去触发函数了,再次触发时候的这次也算是首次,它不能马上被执行。(想象就是不断滑动滚轮10s,然后放下鼠标去喝口水,再回来滑滚轮,那应该算作新的一次开始,而不是上次的继续)
        result = func.apply(context, args);
        timeout =  context = args = null;
    };
    

    但是如果第二次触发与第一次触发的时间间隔大于wait时候就会进入到

    // 实际上remaining<=0就足够了,后者是考虑到假如客户端修改了系统时间则马上执行func函数
    if (remaining <= 0 || remaining > wait) {
        // 取消第一次的setTimeout
        if (timeout) {
            clearTimeout(timeout);
            timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
    }
    其实也应该写成这样更好理解
    if (remaining <= 0 || remaining > wait) {
        if (timeout) {
            clearTimeout(timeout);
        }
        previous = now;
        result = func.apply(context, args);
        timeout = context = args = null;
    }
    

    有个疑问就是imeout = setTimeout(later, remaining), remaining等于wait,如果两次时间间隔十分接近wait的又大于wait应该是怎么样的流程呢。个人觉得应该是进入到上面这个代码块然后clearTimeout, 为什么呢,首先javaScript是单线程的,setTimeout的意思是将函数在wait毫秒后添加到任务队列中,而不是立即执行。所以理论上来讲还是进入上述代码块要比在执行later()早。但是想想如果每次都是setTimeout也行,每隔wait运行later,效果差不多。

    小结

    所以第三个参数不传入就是leading模式。
    {trailing: false}也是leading模式但和不传参数还是有点区别就是它无法执行timeout = setTimeout(later, remaining);

    {leading: false}就是trailing模式,他的timeout = setTimeout(later, remaining);实际上是timeout = setTimeout(later, wait)

  • 相关阅读:
    企业移动视频通话会议EasyRTC视频会议通话系统开拓视频会议行业新前景
    安防网络摄像头海康大华硬盘录像机视频流媒体服务器EasyNVR调用接口时提示未授权问题解决方案
    安防RTSP_Onvif网络摄像头互联网直播视频流媒体服务器在使用过程中如何保存用户登录时的信息
    RTSP、RTMP、HTTP-FLV、 HLS安防网络摄像头互联网直播音视频流媒体服务器EasyNVR如何实现密码的MD5加密
    安防RTSP_Onvif网络摄像头互联网直播视频流媒体服务器EasyNVR如何解决视频流Ajax跨域访问的问题
    Python之网路编程利用threading模块开线程
    Python之网路编程之线程介绍
    Python之网路编程之进程池及回调函数
    Python之网路编程之-互斥锁与进程间的通信(IPC)及生产者消费者模型
    Python之网路编程利用multiprocessing开进程
  • 原文地址:https://www.cnblogs.com/guanine/p/9623325.html
Copyright © 2020-2023  润新知