Ajax(转)JavaScript-性能优化的函数节流(throttle)与函数去抖(debounce)

 JavaScript-性能优化的函数节流(throttle)与函数去抖(debounce)

       
函数省流,简单地讲话,就是给一个函数无法以大缺的时日距离内连调用,只有当上一致浅函数执行后了了你规定的时光间隔,才会拓展下一致潮该函数的调用。

       
函数节约流的原理非常简单的,估计大家还想开了,那就是是定时器。当自身点一个时光常,先setTimout让这波延一会面再次实行,如果在这时刻距离内又点了轩然大波,那咱们即便clear掉原来的定时器,再setTimeout一个初的定时器延迟一碰头执行,就这样。

以下场景往往出于事件一再被硌,因而频繁执行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性能,建议在使上述和类似事件之时段用函数节流或函数去抖加以控制。

季、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

相关文章