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

 更新时间:2016年08月11日 11:06:01   作者:苏服  
这篇文章主要介绍了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函数的延时执行。

以上所述是小编给大家介绍的JavaScript性能优化之函数节流(throttle)与函数去抖(debounce),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

相关文章

  • BootStrap注意事项小结(五)表单

    BootStrap注意事项小结(五)表单

    这篇文章主要介绍了BootStrap注意事项小结(五)表单的相关资料,非常不错,具有参考借鉴价值,,需要的朋友可以参考下
    2017-03-03
  • 移动端脚本框架Hammer.js

    移动端脚本框架Hammer.js

    这篇文章主要为大家详细介绍了一款开源的移动端脚本框架Hammer.js,可以完美的实现在移端开发的大多数事件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • COM组件中调用JavaScript函数详解及实例

    COM组件中调用JavaScript函数详解及实例

    这篇文章主要介绍了COM组件中调用JavaScript函数详解及实例的相关资料,需要的朋友可以参考下
    2017-02-02
  • TypeScript学习笔记之类型缩小

    TypeScript学习笔记之类型缩小

    在TypeScript中若一个变量使用了联合类型,那么当我们使用该变量时必不可少的会去明确的限制该变量的具体类型,这称为类型缩小,这篇文章主要给大家介绍了关于TypeScript学习笔记之类型缩小的相关资料,需要的朋友可以参考下
    2022-09-09
  • js一维数组、多维数组和对象的混合使用方法

    js一维数组、多维数组和对象的混合使用方法

    这篇文章主要介绍了js一维数组、多维数组和对象的混合使用方法,需要的朋友可以参考下
    2016-04-04
  • [JSF]使用DataModel处理表行事件的实例代码

    [JSF]使用DataModel处理表行事件的实例代码

    在使用JSF中,最常用的恐怕就要属于表格的处理了。使用DataModel可以方便地进行对表行的处理:
    2013-08-08
  • js实现交通灯效果

    js实现交通灯效果

    本文主要介绍了js实现交通灯效果的示例代码。具有一定的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • IE8对JS通过属性和数组遍历解析不一样的地方探讨

    IE8对JS通过属性和数组遍历解析不一样的地方探讨

    如果是非IE8浏览器例如(IE7、IE9、Chrome、FF,仅测试这几种)通过属性和数组遍历,其结果是一样的,但对于IE8,结果会有一点小小的差异,在IE8下会把原型链扩展方法当做一个属性输出,大家可以测试一下
    2013-05-05
  • JS格式化数字金额用逗号隔开保留两位小数

    JS格式化数字金额用逗号隔开保留两位小数

    JS格式化数字金额只留两位小数。写了个格式化函数。可以控制小数位数,自动四舍五入,感兴趣的朋友可以了解下
    2013-10-10
  • BootStrap表单验证实例代码

    BootStrap表单验证实例代码

    这篇文章主要介绍了bootstrap表单验证的实例代码,代码中包括引入的js文件,具体实现方法,大家参考本文
    2017-01-01

最新评论