美文网首页collections
JS中的节流和去抖动

JS中的节流和去抖动

作者: 圭宁_2ce3 | 来源:发表于2017-09-22 15:48 被阅读695次

    首先要明白 节流 Throttle 和 去抖动 Debounce 两者是有区别的,很多人一开始都会搞混。
    先讲讲去抖动 Debounce

    Debounce

    为什么要去抖动?

    我们知道 浏览器有一些原生事件,比如 resize scroll keyup keydown 这些事件的回调函数,当他们触发的时候,并不是想象中的只触发一次,而是几次甚至几十次,如果当你的这些事件回调函数中有一些复杂的运算或者dom操作,低配浏览器很容易出现假死的状态。

    去抖动Debounce实现的效果是:以scroll来举例,当scroll回调在指定的时间n毫秒内还会触发,此次回调方法不执行,继续等待n毫秒,直到n毫秒之后此方法不再触发,执行这个方法。简单来说就是:把在指定时间内可能会多次执行的方法打包成一次

    window.debounce = function(fun,dely){  //fun 需要去抖动的方法,dely 指定的延迟时间
        var timer = null;  // 用闭包维护一个timer 做为定时器标识
        return function(){
            var context = this;  // 调用debounce的时候 保存执行上下文
            var args = arguments;  
            clearTimeout(timer);
            timer = setTimeout(function() {
                fun.apply(context , args);
             }, delay); // 设定定时器 判断是否已经触发 ,如果触发则重新计时 等待dely毫秒再执行
        }
    }
    

    此时如果调用

    foo = function(){
        console.log('scroll work')
    }
    dom.addEventListener('scroll', debounce(foo, 2000)); // 当dom连续触发scroll 时 回调函数只会在两秒后执行一次 
    

    但是这种写法有一个明显的缺陷,就是当用户触发的第一时间方法是不会调用,所以上升级版

    window.debounce = function(fun,dely){ 
        var timer = null; 
        return function(){
            var context = this;  
            var args = arguments;  
            if(timer) { clearTimeout(timer) }; // 看似多余的 但是是必须的 读者可以自己思考为什么需要这么处理
            var doNow = !timer; // 判断是否有定时器,如果有,就dely后清除timer,否则立即执行;
            timer = setTimeou(function(){
                timer = null ;
            },dely)
            if(doNow){
                fun.apply(context, args);
            }
        }
    }
    

    现在的效果是,你滚动的第一时间会触发回调,然后你要是连续再触发,在dely秒之内是不会触发的,只有等dely毫秒后 timer 清除了,再触发滚动才会调用回调。

    想必两个版本的问题大家都看出来了,多多少少都是有点奇怪。接下来就是节流登场了

    Throttle

    节流函数是处理类似场景但抖动不适合的另一种解决方案,比如大型电商网站当用户滚动到页面底部的时候再发AJAX请求获取图片,实现图片懒加载,如果使用去抖动,不管方案一还是二,都会用种奇怪的体验,假设设置500ms的delay时间,使用方案一,效果则是,用户滚动了,500ms后发AJAX获取图片,再显示图片。期间500ms用户是只能看到图片缺失的。如果使用方案二,似乎是能实现需求,但是仔细想想,如果用户不是500ms滚动一次,而是玩命的在连续滚动,则AJAX只会触发一次,用户只能看到第一次滚动触发AJAX返回的图片,后面的则是图片缺失状态。

    到这应该可以猜到节流实现的什么效果了。

    节流函数允许一个函数在规定的时间内只执行一次。

    它和防抖动最大的区别就是,节流函数不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。

    主要有两种实现方法:

    1.时间戳
    2.定时器

    时间戳实现:
    window.throttle = function(fun,delay){
        var prev = Date.now(); // 闭包维护一个起始时间戳 
        return function(){
            var context = this;
            var args = arguments;
            var now = Date.now();  // 每次任务函数触发的时候获取时间戳
            if(now-prev>=delay){ // 判断当前时间与起始时间戳的间隔 大余delay则触发任务函数
                fun.apply(context,args);
                prev = Date.now(); // 关键是要更新闭包中的 起始时间戳
            }
        }
    }
    

    此时我们再测试

    foo = function(){
        console.log('scroll work')
    }
    dom.addEventListener('scroll', throttle (foo, 1000)); // 当dom连续触发scroll 时 任务函数每隔1秒也会触发一次,当然眼尖朋友会发现有个小瑕疵
    
    定时器实现:
    var throttle = function(fun,delay){
        var timer = null; // 维护一个定时器
        return function(){
            var context = this;
            var args = arguments;
            if(!timer){ // 当任务函数触发了 , 判断定时器是否存在  不存在才执行任务函数
                timer = setTimeout(function(){ 
                    fun.apply(context,args);
                    timer = null;
                },delay);  // 当定时器不存在的时候 delay秒后才执行任务函数 并且清空定时器 接着下个轮回
            }
        }
    }
    

    当第一次触发事件时,肯定不会立即执行函数,而是在delay秒后才执行。
    之后连续不断触发事件,也会每delay秒执行一次。
    当最后一次停止触发后,由于定时器的delay延迟,可能还会执行一次函数。

    可以综合使用时间戳与定时器,完成一个事件触发时立即执行,触发完毕还能执行一次的节流函数:

    window.throttle = function(fun,delay){
        var timer = null;
        var startTime = Date.now();  
    
        return function(){
            var curTime = Date.now();
            var remaining = delay-(curTime-startTime);  // 计算出两次触发的时间间隔有没有大余delay 
            var context = this;
            var args = arguments;
    
            clearTimeout(timer);
            if(remaining<=0){ 
                func.apply(context,args);
                startTime = Date.now();  // 如果两次触发时间大余delay,则立马触发一次任务函数并且更新起始时间戳
            }else{
                timer = setTimeout(fun,remaining);  // 如果两次触发时间小于delay, 则改变定时器时间保证delay时间一定触发任务函数
            }
        }
    }
    

    总结

    防止一个事件频繁触发回调函数的方式:

    • 防抖动debounce:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

    • 节流throttle :使得一定时间内只触发一次函数。
      它和防抖动最大的区别就是,节流函数不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而防抖动只是在最后一次事件后才触发一次函数。
      原理是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。

    ---end

    相关文章

      网友评论

        本文标题:JS中的节流和去抖动

        本文链接:https://www.haomeiwen.com/subject/vhzxextx.html