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函数的延时执行。
参考链接:

最新文章

  1. 16个基本颜色关键字 Basic color keywords
  2. CSS里的pointer-events属性
  3. 自定义弹出框基于zepto 记得引入zepto
  4. PHP模拟发送POST请求之二、用PHP和JS处理URL信息
  5. Oracle 操作 - 配置
  6. Simple guide to Java Message Service (JMS) using ActiveMQ
  7. PHP 错误提示
  8. leetcode383
  9. ubuntu安装新版QQ
  10. hdu 2669 Romantic 扩展欧几里得
  11. Kubernetes 1.10.0离线安装
  12. MySQL的一些指令操作[简版]
  13. Android开发三种第三方图片加载的框架
  14. [Java] SpringMVC工作原理之四:MultipartResolver
  15. Spring事务的5种隔离级别和7种传播性
  16. swift class的虚函数表、扩展、@objc修饰、虚函数的派发方式研究
  17. Android开发--ZZ:Android APK反编译详解(附图)
  18. ngIf 和 template的结合使用
  19. Wannafly挑战赛22游记
  20. zookeeper与kafka安装部署及java环境搭建(发布订阅模式)

热门文章

  1. poj 2777(线段树的节点更新策略)
  2. Windows Azure HandBook (9) Azure性能测试(2)
  3. 分享本年度最佳的15个 Photoshop 实例教程
  4. SQL Server代理(1/12):配置和概况
  5. JavaScript可否多线程? 深入理解JavaScript定时机制
  6. CSS魔法堂:你真的理解z-index吗?
  7. [译]使用scikit-learn进行机器学习(scikit-learn教程1)
  8. C#中方法的声明
  9. Linux ACL
  10. ASP.NET MVC系列:添加模型的验证规则