加载中...
  • 解读js函数防抖与函数节流

    Underscore.js 是一个非常强大的JS库,今天针对场景的函数防抖和函数节流做一个总结。

    前言

    防抖(Debounce)和节流(throttle)都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似而又不同。

    应用场景

    防抖场景

    连续的事件,只需触发一次回调的场景有:
    1.搜索框搜索输入。只需用户最后一次输入完,再发送请求;
    2.手机号、邮箱验证输入检测;
    3.窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

    节流场景

    间隔一段时间执行一次回调的场景有:

    1.滚动加载,加载更多或滚到底部监听;
    2.谷歌搜索框,搜索联想功能;
    3.高频点击提交,表单重复提交。

    函数防抖

    如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。
    类似表单提交or登陆的业务操作,包括注册框的正则验证,如果一个事件被频繁触发多次,并且触发的时间间隔过短,则防抖函数可以使得对应的事件处理函数只执行一次,减少验证时间和页面过度请求响应

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    /**
    * 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
    *
    * @param {function} func 传入函数
    * @param {number} wait 表示时间窗口的间隔
    * @param {boolean} immediate 设置为ture时,调用触发于开始边界而不是结束边界
    * @return {function} 返回客户调用函数
    */
    _.debounce = function(func, wait, immediate) {
    var timeout, args, context, timestamp, result;

    var later = function() {
    // 据上一次触发时间间隔
    var last = _.now() - timestamp;

    // 上次被包装函数被调用时间间隔last小于设定时间间隔wait
    if (last < wait && last > 0) {
    timeout = setTimeout(later, wait - last);
    } else {
    timeout = null;
    // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
    if (!immediate) {
    result = func.apply(context, args);
    if (!timeout) context = args = null;
    }
    }
    };

    return function() {
    context = this;
    args = arguments;
    timestamp = _.now();
    var callNow = immediate && !timeout;
    // 如果延时不存在,重新设定延时
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
    result = func.apply(context, args);
    context = args = null;
    }

    return result;
    };
    };

    整体函数实现的不难,总结一下。
    对于按钮防点击来说的实现:一旦我开始一个定时器,只要我定时器还在,不管你怎么点击都不会执行回调函数。一旦定时器结束并设置为 null,就可以再次点击了。
    对于延时执行函数来说的实现:每次调用防抖动函数都会判断本次调用和之前的时间间隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间减去之前的时间间隔。一旦时间到了,就会执行相应的回调函数。

    函数节流

    防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    /**
    * 频率控制 返回函数连续调用时,func 执行频率限定为 次 / wait
    *
    * @param {function} func 传入函数
    * @param {number} wait 表示时间窗口的间隔
    * @param {object} options 如果想忽略开始边界上的调用,传入{leading: false}。
    * 如果想忽略结尾边界上的调用,传入{trailing: false}
    * @return {function} 返回客户调用函数
    */
    _.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    // 上次执行时间点
    var previous = 0;
    if (!options) options = {};
    // 延迟执行函数
    var later = function() {
    // 若设定了开始边界不执行选项,上次执行时间始终为0
    previous = options.leading === false ? 0 : _.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
    };
    return function() {
    var now = _.now();
    // 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。
    if (!previous && options.leading === false) previous = now;
    // 延迟执行时间间隔
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口
    // remaining大于时间窗口wait,表示客户端系统时间被调整过
    if (remaining <= 0 || remaining > wait) {
    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;
    };
    };

    异同

    简单来说:

    防抖:把触发非常频繁的事件(比如按键)合并成一次执行。

    节流:保证每 X 毫秒恒定的执行次数,间断执行。

    相同点:

    都可以通过使用 setTimeout 实现。
    目的都是,降低回调执行频率。节省计算资源。

    不同点:

    函数防抖,在一段连续操作结束后,处理回调,利用 clearTimeout 和 setTimeout 实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。
    函数防抖关注一定时间连续触发,只在最后执行一次,而函数节流侧重于一段时间内只执行一次。

    用法
    _.throttle 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function log( event ) {
    console.log( $(window).scrollTop(), event.timeStamp );
    };

    // 控制台记录窗口滚动事件,触发频率比你想象的要快
    $(window).scroll( log );

    // 控制台记录窗口滚动事件,每250ms最多触发一次
    $(window).scroll( _.throttle( log, 250 ) );
    _.debounce 使用示例
    function ajax_lookup( event ) {
    // 对输入的内容$(this).val()执行 Ajax 查询
    };

    // 字符输入的频率比你预想的要快,Ajax 请求来不及回复。
    $('input:text').keyup( ajax_lookup );

    // 当用户停顿250毫秒以后才开始查找
    $('input:text').keyup( _.debounce( ajax_lookup, 250 ) );

    参考文献

    nderscore.js 备注:是一个很精干的库,压缩后只有5.2KB。它提供了几十种函数式编程的方法,弥补了标准库的不足,大大方便了JavaScript的编程。防抖和节流也在其中,还有一些好用的兼容工具。

    作者:程序员思语
    链接:https://juejin.cn/post/6978664839610630181
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    上一篇:
    使用软链 npm link
    下一篇:
    脚手架设计(一)
    本文目录
    本文目录