(转)JavaScript-质量优化之函数节流(throttle)与函数去抖(debounce)

 JavaScript-品质优化之函数节流(throttle)与函数去抖(debounce)

       
函数节流,简单地讲,就是让一个函数不能在很短的光阴世隔内一连调用,唯有当上三次函数执行后过了你确定的大运间隔,才能开展下一次该函数的调用。

       
函数节流的原理挺不难的,推测我们都想到了,那就是定时器。当自家接触一个光阴时,先set提姆out让这些事件延迟一会再实践,如若在这几个小时距离内又触及了事件,那大家就clear掉原来的定时器,再set提姆eout一个新的定时器延迟一会执行,就像是此。

以下场景往往出于事件往往被触发,因此频繁执行DOM操作、资源加载等重表现,导致UI停顿竟然浏览器崩溃。

  1.
window对象的resize、scroll事件

  2. 拖拽时的mousemove事件

  3.
发射游戏中的mousedown、keydown事件

  4. 文字输入、自动已毕的keyup事件

         
实际上对于window的resize事件,实际必要大多为平息改变大小n飞秒后举办后续处理;而别的事件大多的要求是以一定的功效执行后续处理。针对那三种需要就出现了debounce和throttle三种解决办法。

throttle 和 debounce
是化解请求和响应速度不合营难题的五个方案。二者的出入在于采纳分歧的政策。

        throttle 等日子
间隔执行函数。

        debounce 时间间隔 t
内若重新接触事件,则重新计时,直到截至时间超出或等于 t
才实施函数。

一、throttle函数的简练完结

function throttle(fn, threshhold, scope) {
    threshhold || (threshhold = 250);
    var last,
        timer;
    return function () {
        var context = scope || this;
        var now = +new Date(),
            args = arguments;
        if (last && now - last + threshhold < 0) {
            // hold on to it
            clearTimeout(deferTimer);
            timer = setTimeout(function () {
                last = now;
                fn.apply(context, args);
            }, threshhold);
        } else {
            last = now;
            fn.apply(context, args);
        }
    };
}

调用方法

$(‘body’).on(‘mousemove’,
throttle(function (event) {

  console.log(‘tick’);

}, 1000));

二、debounce函数的简短达成

function debounce(fn, delay) {
    var timer = null;
    return function () {
        var context = this, args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function () {
            fn.apply(context, args);
        }, delay);
    };
}

调用方法

$(‘input.username’).keypress(debounce(function
(event) {

  // do the Ajax request

}, 250));

三、简单的卷入达成

/**
  * throttle 
  * @param fn, wait, debounce
  */
var throttle = function ( fn, wait, debounce ) {
    var timer = null, // 定时器
        t_last = null, // 上次设置的时间
        context, // 上下文
        args, // 参数
        diff; // 时间差
    return funciton () {
        var curr = + new Date();
        var context = this, args = arguments;
        clearTimeout( timer );
        if ( debounce ) { // 如果是debounce
            timer = setTimeout( function () {
                fn.apply( context, args );
            }, wait );
        } else { // 如果是throttle
            if ( !t_last ) t_last = curr;
            if ( curr - t_last &gt;= wait ) {
                fn.apply( context, wait );
                context = wait = null;
            }
        }
    }
}
/**
  * debounce
  * @param fn, wait
  */
var debounce = function ( fn, wait ) {
    return throttle( fn, wait, true );
}

小结:那五个艺术适用于会重新触发的部分事件,如:mousemove,keydown,keyup,keypress,scroll等。如若只绑定原生事件,不加以控制,会使得浏览器卡顿,用户体验差。为了增长js性能,提出在运用上述及类似事件的时候用函数节流或者函数去抖加以控制。

Ajax,四、underscore
v1.7.0有关的源码剖析
                          

1. _.throttle函数

_.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;     // 定时器
    var previous = 0;       // 上次触发的时间
    if (!options) options = {};
    var later = function() {
        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 &amp;&amp; options.leading === false) previous = now;

        // 这里引入了一个remaining的概念:还剩多长时间执行事件
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        // remaining &lt;= 0 考虑到事件停止后重新触发或者
        // 正好相差wait的时候,这些情况下,会立即触发事件
        // remaining &gt; wait 没有考虑到相应场景
        // 因为now-previous永远都是正值,且不为0,那么
        // remaining就会一直比wait小,没有大于wait的情况
        // 估计是保险起见吧,这种情况也是立即执行
        if (remaining &lt;= 0 || remaining &gt; wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            result = func.apply(context, args);
            if (!timeout) context = args = null;

            // 是否跟踪
        } else if (!timeout &amp;&amp; options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
        return result;
    };
};

由上可见,underscore考虑了比较多的情况:
options.leading: 第一次是否执行,默认为true,表示第一次会执行,传入{leading:false}则禁用第一次执行
options.trailing:最后一次是否执行,默认为true,表示最后一次会执行,传入{trailing: false}表示最后一次不执行
所谓第一次是否执行,是刚开始触发事件时,要不要先触发事件,如果要,则previous=0,remaining 为负值,则立即调用了函数
所谓最后一次是否执行,是事件结束后,最后一次触发了此方法,如果要执行,则设置定时器,即事件结束以后还要在执行一次。
remianing > wait 表示客户端时间被修改过。

2. _.debounce函数 

_.debounce = function(func, wait, immediate) {
    // immediate默认为false
    var timeout, args, context, timestamp, result;
    var later = function() {
        // 当wait指定的时间间隔期间多次调用_.debounce返回的函数,则会不断更新timestamp的值,导致last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func
        var last = _.now() - timestamp;
        if (last < wait && last >= 0) {
            timeout = setTimeout(later, wait - last);
        } else {
            timeout = null;
            if (!immediate) {
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            }
        }
    };
    return function() {
        context = this;
        args = arguments;
        timestamp = _.now();
        // 第一次调用该方法时,且immediate为true,则调用func函数
        var callNow = immediate && !timeout;
        // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
            result = func.apply(context, args);
            context = args = null;
        }
        return result;
    };
};  

 _.debounce实现的精彩之处我认为是通过递归启动计时器来代替通过调用clearTimeout来调整调用func函数的延时执行。

参考链接:

http://www.cnblogs.com/fsjohnhuang/p/4147810.html

http://www.alloyteam.com/2015/10/turning-to-javascript-series-from-settimeout-said-the-event-loop-model/#prettyPhoto

相关文章