最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 还有人不知道防抖节流?(三)

    正文概述 掘金(蒲月阿七)   2021-02-14   441

    昨天分析了防抖函数的源码,今天来看看节流。

    所谓节流,其实就像是控制水龙头的水不要一下子流太多,所以控制它让它缓缓按一定的流速去流下来。也就是在持续触发的情况下控制函数按一定的时间持续执行。

    先来看看它是怎么使用的。

    // 用节流函数包装要执行的函数doSomething
    // _.throttle(func, [wait=0], [options={}])
    let doSome = throttle(doSomeThing, 1000, {
        leading: true,
        trailing: true
    })
    // 鼠标移动到container时就触发
    container.onmousemove = doSome;
    

    可以发现,它除了需要执行函数,延迟时间,还有第三个参数,这是一个对象。

    opitons参数中定义了一些选项:

    • leading,函数在每个等待时延的开始被调用,默认值为false
    • trailing,函数在每个等待时延的结束被调用,默认值是true

    根据leadingtrailing的组合,可以实现不同的调用效果:

    • 第一种:leading-true, trailing-false:只在延时开始时调用,延时结束后不调用
    • 第二种:leading-false,trailing-true:默认情况,即在延时结束后才会调用函数
    • 第三种:leading-true,trailing-true:在延时开始时就调用,延时结束后也会调用

    注意:没有leading-false,trailing-false的情况噢!

    我们可以先来看看第一种情况是怎么实现的。要在一开始立即调用,结束的时候不调用,我们可以利用时间戳来实现。刚开始要满足现在时间与之前时间之差要大于延迟时间,后面触发的时候如果差值没有超过延迟时间,就不执行最后一次了。

    刚开始我们可以直接获取当前时间,把之前时间定为0,此时两个值之差一定是大于延迟时间的,也就是满足可以立即执行。

    // 刚开始会立即触发,但是一旦在未达到触发时间时停止触发,就不执行函数。
    // 相当于:
    // let doSome = _.throttle(doSomeThing, 1000, {
    //     leading: true,
    //     trailing: false
    // });
    // 第一次触发,最后不会调用触发函数
    function throttle(func, wait){
        let context, args;
    
        // 之前的时间戳
        let old = 0;
    
        return function(){
            // 改变内部this指向
            context = this;
            // 将参数传给真正执行的函数,目的是获取event事件对象
            args = arguments;
    
            // 获取当前的时间戳
            let now = new Date().valueOf();
    
            // 如果当前时间减去前一次触发事件等于延迟时间,则执行函数
            if(now - old > wait){
                // 立即执行
                func.apply(context, args);
                // 同时将当前时间赋值给旧时间
                old = now;
            }
        }
    
    

    接下来看看第二种情况。

    // 相当于:
    // let doSome = _.throttle(doSomeThing, 1000, {
    //     leading: false,
    //     trailing: true
    // });
    // 第一次不会触发,最后一次会触发
    function throttle(func, wait){
        let context, args, timeout;
    
        return function(){
            // 改变内部this指向
            context = this;
            // 将参数传给真正执行的函数,目的是获取event事件对象
            args = arguments;
    
            // 如果还没有设置定时器,就设置定时器延时执行
            if(!timeout){
                timeout = setTimeout(() => {
                    // 等到到达延迟时间的时候,就把延时器置空,然后执行函数。
                    // 定时器置空之后,如果继续触发,就会继续设置定时器,循环这个过程
                    timeout = null; 
                    func.apply(context, args);
                }, wait);
            }
        }
    }
    // 我们可以发现,刚开始触发的时候是没有立即执行的,因为是设置定时器延时执行。
    // 但是最后的时候如果定时器置空之后继续触发这个函数,即使还没有到达延时时间,还是会重新设置定时器,等到到达延时时间后会再执行一次。
    

    最后我们来看看源码是怎么实现将这两个结合在一起的!

    _.throttle = function(func, wait, options) {
    
      var timeout, context, args, result;
      
      // 之前的时间戳
      var previous = 0;
      
      // 如果没有传第三个参数,就将其设置为空对象,防止访问出现问题
      if (!options) options = {};
      
      // 清空定时器,立即执行,注意这个函数是在!timeout && options.trailing !== false情况下触发的,也就是顾尾而且此时定时器为空
      var later = function() {
      
      // 如果leading为false,也就是刚开始没有立即执行,顾尾不顾头,那就设置为0,否则顾尾也顾头,重新获取当前时间将其赋给他,更新previous的值
        previous = options.leading === false ? 0 : _.now();
        
        // 清空定时器
        timeout = null;
        // 立即执行函数
        result = func.apply(context, args);
        // 防止内存泄漏
        if (!timeout) context = args = null;
      };
    
      var throttled = function() {
      // 获取当前时间
        var now = _.now();
        
        // 如果此时leading为false,也就是不立即执行,同时previous为空,意味着第一次执行或者是设置了定时器(later里面有判断),那么就把之前时间设置为现在时间,防止时间差影响判断。
        // 此时remaining结果会等于wait,不会进入第一个判断
        if (!previous && options.leading === false) previous = now;
        
        // 距下一次执行剩下的时间:延时时间减去(现在时间和过去时间的差值)
        var remaining = wait - (now - previous);
        
        // 改变内部this指向
        context = this;
        // 将参数传给真正执行的函数,目的是获取event事件对象
        args = arguments;
        
        // 剩下时间小于等于0(第一种情况,通过时间戳触发执行)或者大于延时时间(过去时间比现在时间还要晚)
        if (remaining <= 0 || remaining > wait) {
        // 已经有定时器了就直接清空,防止时间戳方式和定时器方式冲突,同时触发两种
          if (timeout) {
            clearTimeout(timeout);
            timeout = null;
          }
          // 将现在时间赋值给之前的时间
          previous = now;
          // 立即执行并将执行结果返回给result
          result = func.apply(context, args);
          // 防止内存泄漏
          if (!timeout) context = args = null;
          
        } else if (!timeout && options.trailing !== false) {
        // 相当于第二种情况,顾尾而且此时定时器为空了,将重新设置定时器
          timeout = setTimeout(later, remaining);
        }
        
        // 返回原来执行函数的结果
        return result;
      };
      
      // 如果wait延迟执行的时间比较长的话,可以调用cancel中途取消函数执行。
      throttled.cancel = function() {
        clearTimeout(timeout);	// 清空定时器
        previous = 0;	// 把之前的时间也重置为0
        timeout = context = args = null;	// 防止内存泄漏
      };	
      
      // 返回包装后的函数对象
      return throttled;
    };
    

    起源地下载网 » 还有人不知道防抖节流?(三)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元