【 js 性能优化】throttle 与 debounce 节流
在看 underscore.js 源码的时候,接触到了这样两个方法,很有意思:
我先把实现的代码撂在下面,看不懂的可以先跳过,但是跳过可不是永远跳过哦~
一个是 throttle:
_.throttle = function(func, wait, options) {
var context, args, result;
// setTimeout 的 handler
var timeout = null;
// 标记时间戳
// 上一次执行回调的时间戳
var previous = 0;
// 如果没有传入 options 参数
// 则将 options 参数置为空对象
if (!options)
options = {};
var later = function() {
// 如果 options.leading === false
// 则每次触发回调后将 previous 置为 0
// 否则置为当前时间戳
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
// 这里的 timeout 变量一定是 null 了吧
// 是否没有必要进行判断?
if (!timeout)
context = args = null;
};
// 以滚轮事件为例(scroll)
// 每次触发滚轮事件即执行这个返回的方法
// _.throttle 方法返回的函数
return function() {
// 记录当前时间戳
var now = _.now();
// 第一次执行回调(此时 previous 为 0,之后 previous 值为上一次时间戳)
// 并且如果程序设定第一个回调不是立即执行的(options.leading === false)
// 则将 previous 值(表示上次执行的时间戳)设为 now 的时间戳(第一次触发时)
// 表示刚执行过,这次就不用执行了
if (!previous && options.leading === false)
previous = now;
// 距离下次触发 func 还需要等待的时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 要么是到了间隔时间了,随即触发方法(remaining <= 0)
// 要么是没有传入 {leading: false},且第一次触发回调,即立即触发
// 此时 previous 为 0,wait - (now - previous) 也满足 <= 0
// 之后便会把 previous 值迅速置为 now
// ========= //
// remaining > wait,表示客户端系统时间被调整过
// 则马上执行 func 函数
// @see https://blog.coding.net/blog/the-difference-between-throttle-and-debounce-in-underscorejs
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
// 解除引用,防止内存泄露
timeout = null;
}
// 重置前一次触发的时间戳
previous = now;
// 触发方法
// result 为该方法返回值
result = func.apply(context, args);
// 引用置为空,防止内存泄露
// 感觉这里的 timeout 肯定是 null 啊?这个 if 判断没必要吧?
if (!timeout)
context = args = null;
} else if (!timeout && options.trailing !== false) { // 最后一次需要触发的情况
// 如果已经存在一个定时器,则不会进入该 if 分支
// 如果 {trailing: false},即最后一次不需要触发了,也不会进入这个分支
// 间隔 remaining milliseconds 后触发 later 方法
timeout = setTimeout(later, remaining);
}
// 回调返回值
return result;
};
};
一个是debounce:
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
// 定时器设置的回调 later 方法的触发时间,和连续事件触发的最后一次时间戳的间隔
// 如果间隔为 wait(或者刚好大于 wait),则触发事件
var last = _.now() - timestamp;
// 时间间隔 last 在 [0, wait) 中
// 还没到触发的点,则继续设置定时器
// last 值应该不会小于 0 吧?
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
// 到了可以触发的时间点
timeout = null;
// 可以触发了
// 并且不是设置为立即触发的
// 因为如果是立即触发(callNow),也会进入这个回调中
// 主要是为了将 timeout 值置为空,使之不影响下次连续事件的触发
// 如果不是立即执行,随即执行 func 方法
if (!immediate) {
// 执行 func 函数
result = func.apply(context, args);
// 这里的 timeout 一定是 null 了吧
// 感觉这个判断多余了
if (!timeout)
context = args = null;
}
}
};
// 嗯,闭包返回的函数,是可以传入参数的
// 也是 DOM 事件所触发的回调函数
return function() {
// 可以指定 this 指向
context = this;
args = arguments;
// 每次触发函数,更新时间戳
// later 方法中取 last 值时用到该变量
// 判断距离上次触发事件是否已经过了 wait seconds 了
// 即我们需要距离最后一次事件触发 wait seconds 后触发这个回调方法
timestamp = _.now();
// 立即触发需要满足两个条件
// immediate 参数为 true,并且 timeout 还没设置
// immediate 参数为 true 是显而易见的
// 如果去掉 !timeout 的条件,就会一直触发,而不是触发一次
// 因为第一次触发后已经设置了 timeout,所以根据 timeout 是否为空可以判断是否是首次触发
var callNow = immediate && !timeout;
// 设置 wait seconds 后触发 later 方法
// 无论是否 callNow(如果是 callNow,也进入 later 方法,去 later 方法中判断是否执行相应回调函数)
// 在某一段的连续触发中,只会在第一次触发时进入这个 if 分支中
if (!timeout)
// 设置了 timeout,所以以后不会进入这个 if 分支了
timeout = setTimeout(later, wait);
// 如果是立即触发
if (callNow) {
// func 可能是有返回值的
result = func.apply(context, args);
// 解除引用
context = args = null;
}
return result;
};
};
在开发过程中,经常会遇到处理频率很高的事件或着连续的事件,比如像在 window 的 resize/scroll 事件调用某个事件 A 等,如果不进行处理,这个时候伴随着resize就会执行无数个 事件A ,若事件 A 是个很复杂的函数,需要较多的运算执行时间,响应速度跟不上触发频率,往往会出现延迟,导致假死或者卡顿感,并且很多情况下并不是需要“如此”夸张的频繁调用事件 A。
下面 gif 录制了一个频繁调用 事件 A 的例子:
$(window).on("resize",function(){
console.log("A"); // 事件A
})

我们可以想办法来解决这样的问题,一种方式就是规定一个时间间隔 wait ,每隔固定 wait 固定执行一次函数 A,也就是我们要说的 throttle 方法,另一种方式则是在连续事件结束之后开始 过一个时间间隔 wait 执行一次函数 A,也就是我们要说的 debounce方法。
针对例子 resize 来解释( 假设时间间隔5s ):
throttle 方法:在 resize 的过程中,每隔5s执行一次函数 A;
debounce方法:在 resize 结束之后,过了 5s ,执行一次函数 A,如果没有到 5s ,就又开始 resize ,那么就重新计时,并不执行函数 A;
如果我的这个解释没有理解,那么很有一个很多人用的电梯的例子与生活结合在一起会更加形象,在这里也引用一下:
想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应。假设电梯有两种运行方法 throttle和 debounce ,超时设定为15秒,不考虑容量限制。
throttle方法的电梯。保证如果电梯第一个人进来后,15秒后准时运送一次,不等待。如果没有人,则待机。debounce方法的电梯。如果电梯里有人进来,等待15秒。如果又人进来,15秒等待重新计时,直到15秒超时,开始运送。
下面就逐一说一下两个方法:
1、 throttle 方法:
Underscore.js 中 针对这个方法 传入三个参数:func 即你要在密集事件内调用的函数,也就是例子中的 事件 A,wait 即时间间隔,而第三个参数 options 可以传两种选项,一种是 {leading: false},表示你想禁用第一次首先执行函数 A,如果不传,就表示你想上来先调用一次 函数 A,另一种是 {trailing: false},表示想禁用最后一次执行函数 A ;

(此图来自 http://benalman.com/projects/jquery-throttle-debounce-plugin/)
2、debounce 方法
Underscore.js 中 针对这个方法 传入三个参数:func 即你要在密集事件内调用的函数,也就是例子中的 事件 A,wait 即时间间隔,而第三个参数 immediate 可以传 true 或者 false。传为 true, debounce 会在 wait 时间间隔的开始调用这个函数 (注:并且在 waite 的时间之内,不会再次调用)。在类似不小心点了提交按钮两下而提交了两次的情况下很有用。

(此图来自 http://benalman.com/projects/jquery-throttle-debounce-plugin/,图中 at_begain 即可看为 immediate )
两者使用方法类似:
// WRONG 错误的调用方法
$(window).on('resize', function() {
_.throttle(doSomething, 300);
}); // RIGHT 正确的调用方法
$(window).on('resize', _.throttle(doSomething, 300));
最后,两者既然有区别,那么也有针对不同场景下更好的选择方法:
throttle:
1、DOM 元素动态定位,window 对象的 resize 和 scroll 事件,比如:用户在你无限滚动的页面上向下拖动,你需要判断现在距离页面底部多少。如果用户快接近底部时,我们应该发送请求来加载更多内容到页面。
2、如果用户在 30s 内 input 输入非常块,但你想固定每间隔 5s 就进行一次某个事情。
debounce:
1、AutoComplete中的Ajax请求使用的keypress
2、在进行input校验的时候,“你的密码太短”等类似的信息。
其实选择哪个方法主要取决于你是否想确保在固定的时间间隔进行回调。
别忘了再回头研究一下最上方的源码。
嘿嘿,赞和关注哦~ ε-(´∀`; )
参考并感谢:
https://segmentfault.com/a/1190000004909376 里面有例子,大家可以进去试试
http://benalman.com/projects/jquery-throttle-debounce-plugin/
https://blog.coding.net/blog/the-difference-between-throttle-and-debounce-in-underscorejs
http://drupalmotion.com/article/debounce-and-throttle-visual-explanation

【 js 性能优化】throttle 与 debounce 节流的更多相关文章
- js性能优化-事件委托
js性能优化-事件委托 考虑一个列表,在li的数量非常少的时候,为每一个li添加事件侦听当然不会存在太多性能方面的问题,但是当列表非常的长,长到上百上千甚至上万的时候(当然只是一个解释,实际工作中很少 ...
- js 性能优化利器:prepack
1. js 性能优化 js 本身是没有像 python 一样的预编译功能,更没有像 java 一样的编译功能,所以,这里所说的 js 代码预编译 只是通过工具实现的类似功能而已. 这就要提到 prep ...
- js 性能优化 篇一
JS性能优化 摘自:http://www.china125.com/design/js/3631.htm 首先,由于JS是一种解释型语言,执行速度要比编译型语言慢得多.(注:,Chrome是第一款内 ...
- js性能优化文章集锦
总结的js性能优化方面的小知识http://www.it165.net/pro/html/201503/35336.html 如何优化你的JS代码http://www.php100.com/html/ ...
- 【 js 性能优化】【源码学习】underscore throttle 与 debounce 节流
在看 underscore.js 源码的时候,接触到了这样两个方法,很有意思: 我先把实现的代码撂在下面,看不懂的可以先跳过,但是跳过可不是永远跳过哦- 一个是 throttle: _.throttl ...
- JS性能优化笔记搜索整理
通过网上查找资料了解关于性能优化方面的内容,现简单整理,仅供大家在优化的过程中参考使用,如有什么问题请及时提出,再做出相应的补充修改. 一. 让代码简洁:一些简略的表达方式也会产生很好的优化 eg:x ...
- js性能优化--学习笔记
<高性能网站建设进阶指南>: 1.使用局部变量,避免深入作用域查找,局部变量是读写速度最快的:把函数中使用次数超过一次的对象属性和数组存储为局部变量是一个好方法:比如for循环中的.len ...
- 你不知道的Node.js性能优化,读了之后水平直线上升
本文由云+社区发表 "当我第一次知道要这篇文章的时候,其实我是拒绝的,因为我觉得,你不能叫我写马上就写,我要有干货才行,写一些老生常谈的然后加上好多特技,那个 Node.js 性能啊好像 D ...
- Web篇之JS性能优化
首先,性能优化分好几个方面,本章我们从js方面来优化. 1:垃圾收集 日常中的某些情况下垃圾收集器无法回收无用变量,导致的一个结果就是——内存使用率不断增高,以下为对应的情况以及处理方法. ①对象相互 ...
随机推荐
- 数据结构(Java描述)之二叉树
基础概念 二叉树(binary tree)是一棵树,其中每个结点都不能有多于两个儿子. 二叉排序树或者是一棵空树,或者是具有下列性质的二叉树: (1)若左子树不空,则左子树上所有结点的值均小于或等于它 ...
- bugly集成了Tinker热更新
介绍 热更新能力是Bugly为解决开发者紧急修复线上bug,而无需重新发版让用户无感知就能把问题修复的一项能力.Bugly目前采用微信Tinker的开源方案,开发者只需要集成我们提供的SDK就可以实现 ...
- My97DatePicker.js 之无效日期设置
1.下载并引用My97DatePicker.js 2.将工作日休假的和周末上班的维护到数据库中 3.取出某段时间内的不上班日期集合 /// <summary> ///获取 无效日期集合 1 ...
- SpringMVC:学习笔记(1)——理解MVC及快速入门
SprigMVC-理解MVC及快速入门 说明: 传统MVC-->JSPModel2-->Front Controller + Application Controller + Page C ...
- Xcode插件包Alcatraz
安装命令 curl -fsSL https://raw.github.com/alcatraz/Alcatraz/master/Scripts/install.sh | sh 终于可以了 这个其实 ...
- ARM处理器:开放者的逆袭
作者:Vamei 出处:http://www.cnblogs.com/vamei 严禁转载. 1981年,英国BBC电视台策划了一系列关于计算机的电视节目.但导演发现一个问题:怎么给没见过电脑的观众画 ...
- VS2008编译汇编程序的问题
在VS2008 pro里已经集成了MASM,经过相关设置后,编译出了程序,但是执行后,说少msvcr90.dll39944然后自己写了个39945 那个testprintf是执行正常的.虽然两个都有引 ...
- win8命令行
Win8运行命令.快捷键大全合集 目前看来,Win8还不错,好的产品就是能在短时间改变你使用习惯的产品,首先来给大家讲下windows8 运行命令在哪?其实你可以用Win+R或者你用 WIN+Q再在A ...
- 一张图看懂 JS 的事件机制
一.为什么 JavaScript 单线程 假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准? 为了避免复杂性, JS ...
- ASP.NET Core MVC上传、导入、导出知多少
前言 本君已成夜猫子,本节我们来讲讲ASP.NET Core MVC中的上传,这两天才研究批量导入功能,本节顺便简单搞搞导入.导出,等博主弄妥当了再来和大家一并分享. .NET Core MVC上传 ...