试议常用Javascript 类库中 throttle 与 debounce 辅助函数的区别
问题的引出
看过我前面两篇博客的童鞋可能会注意到都谈到了事件处理的优化问题。
在很多应用中,我们需要控制函数执行的频率,
例如 窗口的 resize,窗口的 scroll 等操作,事件触发的频率非常高,如果处理函数比较复杂,需要较多的计算时间,那么会加重浏览器的负担,这时我们很自然会想到:能否在不影响显示效果(对显示效果影响在可接受范围内)的前提下减少事件响应函数的执行频率呢?
朴素的解决思路
首先我们会想到设置一定的时间范围
delay
,每隔delay
ms 执行不超过一次。
事件处理函数什么时候执行能? 这里有两个选择,一是先执行,再间隔delay
ms来等待;或者是先等待delay
ms,然后执行事件处理函数。操作过程中的事件全不管,反正只执行一次事件处理。
相同低,这一次的事件处理可以是先执行一次,然后后面的事件都不管; 或者前面的都不管,最后操作完了再执行一次事件处理。
当然上面是简单的通俗描述。
throttle 与 debounce 函数现状
throttle与debounce即是对上面两种思路的具体实现。
在现在很多的javascript框架中都提供了这两个函数。例如 jquery中有throttle和debounce插件, underscore.js ,Lo-dash.js 等都提供了这两个函数。
释义
throttle
在英文中是节流阀的意思,顾名思义,就像原本一直流水的龙头,现在每隔一定时间开一下,以节流。
*下图 “|” 表示throttle函数的一次执行, x 表示事件处理函数fn的一次执行*
|||||||||||||||||||||||| (暂停) ||||||||||||||||||||||||
X X X X X X
debounce
bounce是名词弹力,或者动词反弹的意思, de-表否定/相反的前缀,形象低表示一直持续低压抑住不让反弹,当然最后还是得松手,那么反弹一次。
|||||||||||||||||||||||| (暂停) ||||||||||||||||||||||||
X X X X
*上图 “|” 表示debounce函数的一次执行, x 表示事件处理函数fn的一次执行*
区别
throttle 可以想象成阀门一样定时打开来调节流量。 debounce可以想象成把很多事件压缩组合成了一个事件。
有个生动的类比,假设你在乘电梯,没次进一个人需等待10秒钟,不考虑电梯容量限制,那么两种不同的策略是:
谢谢 wonyun ,已改正:
debounce 你在进入电梯后发现这时不远处走来了了一个人,等10秒钟,这个人进电梯后不远处又有个妹纸姗姗来迟,怎么办,再等10秒,于是妹纸上电梯时又来了一对好基友...,作为感动中国好码农,你要每进一个人就等10秒,直到没有人进来,10秒超时,电梯开动。
throttle 电梯很忙,每次就只等10秒,不管是来了妹纸还是好基友,电梯每隔10秒准时送一波人。
因此,简单来说,debounce适合只执行一次的情况,例如 搜索框中的自动完成。在停止输入后提交一次ajax请求;
而throttle适合指定每隔一定时间间隔内执行不超过一次的情况,例如拖动滚动条,移动鼠标的事件处理等。
简单代码实现及实验结果
那么下面我们自己简单地实现下这两个函数:
throttle 函数:
window.addEventListener("resize", throttle(callback, 300, {leading:false}));
window.addEventListener("resize", callback2);
function callback () { console.count("Throttled"); }
function callback2 () { console.count("Not Throttled"); }
/**
* 频率控制函数, fn执行次数不超过 1 次/delay
* @param fn{Function} 传入的函数
* @param delay{Number} 时间间隔
* @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false},
* 如果想忽略结束边界上的调用则传入 {trailing:false},
* @returns {Function} 返回调用函数
*/
function throttle(fn,delay,options) {
var wait=false;
if (!options) options = {};
return function(){
var that = this,args=arguments;
if(!wait){
if (!(options.leading === false)){
fn.apply(that,args);
}
wait=true;
setTimeout(function () {
if (!(options.trailing === false)){
fn.apply(that,args);
}
wait=false;
},delay);
}
}
}
将以上代码贴入浏览器中运行,可得到:
下面再看debounce函数的情况,
debounce 函数:
window.addEventListener("resize", throttle(callback, 300, {leading:false}));
window.addEventListener("resize", callback2);
function callback () { console.count("Throttled"); }
function callback2 () { console.count("Not Throttled"); }
/**
* 空闲控制函数, fn仅执行一次
* @param fn{Function} 传入的函数
* @param delay{Number} 时间间隔
* @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false},
* 如果想忽略结束边界上的调用则传入 {trailing:false},
* @returns {Function} 返回调用函数
*/
function debounce(fn, delay, options) {
var timeoutId;
if (!options) options = {};
var leadingExc = false;
return function() {
var that = this,
args = arguments;
if (!leadingExc&&!(options.leading === false)) {
fn.apply(that, args);
}
leadingExc=true;
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(function() {
if (!(options.trailing === false)) {
fn.apply(that, args);
}
leadingExc=false;
}, delay);
}
}
将以上代码贴入浏览器中运行,分三次改变窗口大小,可看到,每一次改变窗口的大小都会把开始和结束边界的事件处理函数各执行一次:
如果是一次性改变窗口大小,会发现开始和结束的边界各执行一次时间处理函数,请注意与一次性改变窗口大小时 throttle 情况的对比:
underscore.js 的代码实现
_.throttle函数
/**
* 频率控制函数, fn执行次数不超过 1 次/delay
* @param fn{Function} 传入的函数
* @param delay{Number} 时间间隔
* @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false},
* 如果想忽略结束边界上的调用则传入 {trailing:false},
* @returns {Function} 返回调用函数
*/
_.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 && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
_.debounce函数
/**
* 空闲控制函数, fn仅执行一次
* @param fn{Function} 传入的函数
* @param delay{Number} 时间间隔
* @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false},
* 如果想忽略结束边界上的调用则传入 {trailing:false},
* @returns {Function} 返回调用函数
*/
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
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();
var callNow = immediate && !timeout;
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
###参考链接###:
[Debounce and Throttle: a visual explanation](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
[jQuery throttle / debounce: Sometimes, less is more!](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
[underscore.js](http://underscorejs.org/)
试议常用Javascript 类库中 throttle 与 debounce 辅助函数的区别的更多相关文章
- JavaScript ES6中export及export default的区别
相信很多人都使用过export.export default.import,然而它们到底有什么区别呢? 在JavaScript ES6中,export与export default均可用于导出常量.函 ...
- JavaScript ES6中export及export default的区别以及import的用法
本文原创地址链接:http://blog.csdn.net/zhou_xiao_cheng/article/details/52759632,未经博主允许不得转载. 相信很多人都使用过export.e ...
- [C#常用代码]类库中读取解决方案web.Config字符串
对于类库里读取解决方案web.config文件里字符串的方法一.读取键值对的方法:1.添加引用using System.Configuration;2.web.Config配置节<appSett ...
- Javascript 表达式中连续的 && 和 || 之赋值区别
为了区分赋值表达式中出现的连续的 ‘&&’和 ‘||’的不同的赋值含义,做了一个小测试,代码如下: function write(msg){ for(var i = 0; i ...
- 【Java基础】Java开发过程中的常用工具类库
目录 Java开发过程中的常用工具类库 1. Apache Commons类库 2. Guava类库 3. Spring中的常用工具类 4. 其他工具 参考 Java开发过程中的常用工具类库 1. A ...
- 关于Java集合类库中的几种常用队列
Java中几种常用的队列 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞 ...
- 谈谈Java常用类库中的设计模式 - Part Ⅰ
背景 最近一口气看完了Joshua Bloch大神的Effective Java(下文简称EJ).书中以tips的形式罗列了Java开发中的最佳实践,每个tip都将其意图和要点压缩在了标题里,这种做法 ...
- javascript中的throttle和debounce
throttle 我们这里说的throttle就是函数节流的意思.再说的通俗一点就是函数调用的频度控制器,是连续执行时间间隔控制.主要应用的场景比如: 1.鼠标移动,mousemove 事件2.DOM ...
- JavaScript jQuery 中定义数组与操作及jquery数组操作
首先给大家介绍javascript jquery中定义数组与操作的相关知识,具体内容如下所示: 1.认识数组 数组就是某类数据的集合,数据类型可以是整型.字符串.甚至是对象Javascript不支持多 ...
随机推荐
- bash编程-条件测试
Shell脚本中经常需要判断某情况或者数据是否满足,需要由测试机制来实现. 测试方式 echo $?查看命令执行状态返回值 bash脚本中可以自定义返回值exit n(n为自己指定的状态码),shel ...
- yesno孤立词识别kaldi脚本
path.sh主要设定路径等 export KALDI_ROOT=`pwd`/../../.. [ -f $KALDI_ROOT/tools/env.sh ] && . $KALDI_ ...
- Java SE核心之一:常用类,包装类,其他基本数据类型包装类。
在Java继承体系中,java.lang.Object类位于顶端(是所有对象的直接或间接父类).如果一个类没有写extends关键字声明其父类,则该类默认继承java.lang.Object类.Obj ...
- 带你走进CSS定位详解
学习CSS相关知识,定位是其中的重点,也是难点之一,如果不了解css定位有时候都不知道怎么用,下面整理了一下关于定位属性的具体理解和应用方案. 一:定位 定位属性列表 position top bot ...
- 读取.Properties文件以及Spring注解读取文件内容
public class Main { public static void main(String[] args) throws IOException { //创建Properties对象 Pro ...
- Ubuntu 16.04 python和OpenCV安装
Ubuntu 16.04 python和OpenCV安装:最进在做深度学习和计算机视觉的有关内容,因此要在python中用到opencv.我的电脑装的是Ubuntu 16.04,python 2.7和 ...
- Golang Struct 声明和使用
Golang Struct 声明和使用 Go可以声明自定义的数据类型,组合一个或多个类型,可以包含内置类型和用户自定义的类型,可以像内置类型一样使用struct类型 Struct 声明 具体的语法 t ...
- Python模块——HashLib与base64
摘要算法(hashlib) Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等. 什么是摘要算法呢?摘要算法又称哈希算法.散列算法.它通过一个函数,把任意长度的数据转换为一个长度 ...
- python 中range函数的用法
一. range(start,end,step) 二.代码 [code1] for i in range(1,10,2): print("i=",i) [result1] i= 1 ...
- JAVA代理分析
JAVA的动态代理 代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类 ...