JS中的节流和去抖动

首先要明白 节流 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容