JavaScript之防抖(Debounce)和节流(Throttle)

防抖与节流

一、背景

防抖和节流是两种不同的控制一个函数执行次数的方法,其目的都是为了节约计算机资源。
当我们操作DOM的时候,加上节流或者防抖就非常有必要,因为众所周知,操作DOM的开销是非常大的,所以要尽可能减少DOM操作次数。

看下面的在线例子:
https://codepen.io/dcorb/pen/PZOZgB

当鼠标滚动或者拖拽的时候可以轻易地每秒触发30个事件,而且在移动端慢速滚动可以达到每秒100次,要是这么短的时间内每次都触发一个或多个函数,那浏览器应该会卡死崩溃的,所以这就引出了防抖节流

二、防抖(Debounce)

防抖允许我们把多次连续的调用放到一次里面去执行,在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

防抖

试想一下,你正身处一部电梯之中,电梯门开始关闭,然后突然又来了一个人,按下了开门键,电梯门开了,他顺利上了电梯,然后电梯又开始缓慢关闭,但是此时又匆匆跑来了另一个人,电梯门又开了,此时,快要迟到的你心里面开始有点不满了。但是,这其实就是一个防抖的实际例子,电梯尽可能的使其资源利用率达到最高,在尽可能的情况下,载上尽可能多的人。

下面是一个例子:
https://codepen.io/dcorb/pen/KVxGqN

  • 具体实现
function debounce(func,delay) {
    let timer;
    return function(...args) {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            func.apply(this,arguments);
        },delay)
    }
}

Lodash中实现 _.debounce_.throttle 的功能很全面,可以直接使用,其中的 throttle函数是使用 _.debounce 新增 maxWait` 选项来实现的, 有兴趣可以自行查看 源码

  • Resize实例
// Based on http://www.paulirish.com/2009/throttled-smartresize-jquery-event-handler/
$(document).ready(function(){
  
  var $win = $(window);
  var $left_panel = $('.left-panel');
  var $right_panel = $('.right-panel');
  
  function display_info($div) {
    $div.append($win.width() + ' x ' + $win.height() +  '<br>');
  }
                
  $(window).on('resize', function(){
    display_info($left_panel);
  });
  
  $(window).on('resize', _.debounce(function() {
    display_info($right_panel);
  }, 400));
});

可以用来监听浏览器窗口尺寸变化事件。
https://codepen.io/dcorb/pen/XXPjpd

  • 输入验证实例
$(document).ready(function(){

  var $statusKey = $('.status-key');
  var $statusAjax = $('.status-ajax');
  var intervalId;
  
  // Fake ajax request. Just for demo
  function make_ajax_request(e){
      var that = this;
      $statusAjax.html('That\'s enough waiting. Making now the ajax request');
     
      intervalId = setTimeout(function(){
         $statusKey.html('Type here. I will detect when you stop typing');
         $statusAjax.html('');
         $(that).val(''); // empty field
      },2000);
    }
  
  // Event handlers to show information when events are being emitted
    $('.autocomplete')
      .on('keydown', function (){  
      
        $statusKey.html('Waiting for more keystrokes... ');
      clearInterval(intervalId);             
      })
     
  
  // Display when the ajax request will happen (after user stops typing)
    // Exagerated value of 1.2 seconds for demo purposes, but in a real example would be better from 50ms to 200ms
  $('.autocomplete').on('keydown',
       _.debounce(make_ajax_request, 1300));
  });

当用户输入并且发起Ajax请求的时候,_.debounce可以用来实现防抖,比如间隔2秒未检测到用户输入才发起请求。

三、节流(Throttle)

  • 具体实现
    使用定时器
var throttle = function(func, delay) {
    var timer = null;
    return function() {
        if (!timer) {
            timer = setTimeout(function() {
                func.apply(this,arguments);
                timer = null;
            },delay);
        }
    }
}

另一种定时器写法:

let isAllow = true;
function throttle() {
    let fun = function() {
        if (!isAllow)
            return;
        let timer = setTimeout(() => {
            console.log("throttle");
            clearTimeout(timer);
            timer = null;
            isAllow = true;
        },1000);
    };
    fun();
}

二者的区别是后者使用了isAllow标志位来判断是否需要执行函数。

  • 滚动条实例
    一个十分常见的例子,当用户的鼠标滚动的时候,检查鼠标位置离底部的距离,当接近底部时,发起Ajax请求。
    https://codepen.io/dcorb/pen/eJLMxa

requestAnimationFrame

requestAnimationFrame是另一种限制函数执行次数的方式,可以认为与_.throttle类似,但是其拥有更高的准确度,因为其本身就是为了更好的精确度而生的原生API。

requestAnimationFrame的优点

  • 致力于60fps(16ms每帧),自己可以决定最好的渲染时间。
  • 简单且标准的原生API,不太容易改变,减少维护成本。

requestAnimationFrame的缺点

  • 需要手动开始或取消rAFs,.debounce.throttle则不需要。
  • 如果tab没有激活,则不会执行,即使触发 Ascroll, mouse 或者 keyboard 等事件也不行.
  • 不支持 IE9, Opera Mini 和 老版本 Android.
  • 不支持node.js,所以不能在服务端使用。

requestAnimationFrame实例

_.throttle相比,同时设置 16ms, 相同的性能环境下,rAF 可以在更复杂的情况下拥有更好的结果。

https://codepen.io/dcorb/pen/pgOKKw

四、结论

  • 一般来说,如果你的JavaScript函数是直接绘制或者动画某些属性,那么可以使用requestAnimationFrame,在需要重新计算元素位置的地方使用。

  • 需要发起Ajax请求,或者决定是否增加或删除一个类的时候,可以考虑使用_.debounce 或者_.throttle

  • debounce: 在事件被触发x秒后再执行回调,如果在这x秒内又被触发,则重新计时。

  • throttle: 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

  • requestAnimationFrame: 节流的另外一种选项,在函数重新计算或者渲染元素时,需要平滑过渡或者动画的时候使用。

五、参考

https://css-tricks.com/debouncing-throttling-explained-examples/

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

推荐阅读更多精彩内容