Javascript中 节流函数 throttle 与 防抖函数 debounce
问题的引出
在一些场景往往由于事件频繁被触发,因而频繁地进行DOM操作、资源加载,导致UI停顿甚至浏览器崩溃。
在这样的情况下,我们实际上的需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。
1. resize事件
2. mousemove事件
3. touchmove事件
4. scroll事件
throttle 与 debounce
在现在很多的javascript框架中都提供了这两个函数。例如 jquery中有throttle和debounce插件, underscore.js ,Lodash.js 等都提供了这两个函数。
原理:
首先我们会想到设置一定的时间范围
delay
,每隔delay
ms 执行不超过一次。
事件处理函数什么时候执行能? 这里有两个选择,一是先执行,再间隔delay
ms来等待;或者是先等待delay
ms,然后执行事件处理函数。操作过程中的事件全不管,反正只执行一次事件处理。
相同低,这一次的事件处理可以是先执行一次,然后后面的事件都不管; 或者前面的都不管,最后操作完了再执行一次事件处理。
区别:
1. throttle
如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。
也就是会说预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。
2.debounce
如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。
也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。
简单代码实现及实验结果
那么下面我们自己简单地实现下这两个函数:
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
jQuery throttle / debounce: Sometimes, less is more!
underscore.js
Javascript中 节流函数 throttle 与 防抖函数 debounce的更多相关文章
- (转)JavaScript-性能优化之函数节流(throttle)与函数去抖(debounce)
JavaScript-性能优化之函数节流(throttle)与函数去抖(debounce) 函数节流,简单地讲,就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过 ...
- 博文推荐】Javascript中bind、call、apply函数用法
[博文推荐]Javascript中bind.call.apply函数用法 2015-03-02 09:22 菜鸟浮出水 51CTO博客 字号:T | T 最近一直在用 js 写游戏服务器,我也接触 j ...
- JavaScript性能优化之函数节流(throttle)与函数去抖(debounce)
函数节流,简单地讲,就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用. 函数节流的原理挺简单的,估计大家都想到了,那就是定时器.当我 ...
- JS魔法堂:函数节流(throttle)与函数去抖(debounce)
一.前言 以下场景往往由于事件频繁被触发,因而频繁执行DOM操作.资源加载等重行为,导致UI停顿甚至浏览器崩溃. 1. window对象的resize.scroll事件 2. 拖拽时的mousemov ...
- Javascript中call、apply、bind函数
javascript在函数创建的时候除了自己定义的参数外还会自动新增this和arguments两个参数 javascript中函数也是对象,call.apply.bind函数就是函数中的三个函数,这 ...
- JavaScript中bind、call、apply函数用法详解
在给我们项目组的其他程序介绍 js 的时候,我准备了很多的内容,但看起来效果不大,果然光讲还是不行的,必须动手.前几天有人问我关于代码里 call() 函数的用法,我让他去看书,这里推荐用js 写服务 ...
- JavaScript中以构造函数的方式调用函数
转自:http://www.cnblogs.com/Saints/p/6012188.html 构造器函数(Constructor functions)的定义和任何其它函数一样,我们可以使用函数声明. ...
- JavaScript中bind、call、apply函数使用方法具体解释
在给我们项目组的其它程序介绍 js 的时候,我准备了非常多的内容,但看起来效果不大,果然光讲还是不行的,必须动手. 前几天有人问我关于代码里 call() 函数的使用方法.我让他去看书,这里推荐用js ...
- JavaScript中的bind,call和apply函数的用法和区别
一直没怎么使用过JavaScript中的bind,call和apply, 今天看到一篇比较好的文章,觉得讲的比较透彻,所以记录和总结如下 首先要理解的第一个概念,JavaScript中函数调用的方式, ...
随机推荐
- EJB是什么?(节选)
近期的项目中使用了EJB.当时就仅仅知道怎么用,没有深入的去理解.当完毕这个项目之后.再回想项目中使用的技术.框架以及工具的时候,突然感觉对EJB这个概念非常是模糊,于是上网搜一些资料.可是,非常多的 ...
- spring roo初体验
1.下载spring-roo-2.0.0.M1,并执行如下命令,在/usr/local/bin下面建立一个roo的软连接 sudo ln -s /Users/pud/Documents/still ...
- 2017年Android百大框架排行榜(转)
一.榜单介绍 排行榜包括四大类: 单一框架:仅提供路由.网络层.UI层.通信层或其他单一功能的框架 混合开发框架:提供开发hybrid app.h5与webview结合能力.web app能力的框架 ...
- Java应用一般架构
转载一下文章: 自己连看三便方的其要点精髓. 当我们架设一个系统的时候通常需要考虑到如何与其他系统交互,所以我们首先需要知道各种系统之间是如何交互的,使用何种技术实现. 1. 不同系统不同语言之间的交 ...
- Dispose模式释放非托管资源
实现方式用的是设计模式里的模板模式,基类先搭好框架,子类重写void Dispose(bool disposing) 即可. 需要注意的是基类的Finalize函数也就是析构函数调用的是虚函数void ...
- git add -A使用说明
git help add -A, --all Like -u, but match <filepattern> against files in the workin ...
- Spring Cloud 微服务五:Spring cloud gateway限流
前言:在互联网应用中,特别是电商,高并发的场景非常多,比如:秒杀.抢购.双11等,在开始时间点会使流量爆发式地涌入,如果对网络流量不加控制很有可能造成后台实例资源耗尽.限流是指通过指定的策略削减流量, ...
- vue实践---根据不同环境,自动转换请求的url地址
一般的项目环境分为:本地环境,测试环境,预发环境,正式环境. 这些环境的域名一般是一样的, 前端请求接口的url也会随着这些环境的变化而改变,手动修改有点麻烦,所以想个办法,让请求的地址根据域名改变而 ...
- easyui首页模板
Easyui首页html代码 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "htt ...
- 虚拟化构建二分图(BZOJ2080 题解+浅谈几道双栈排序思想的题)
虚拟化构建二分图 ------BZOJ2080 题解+浅谈几道双栈排序思想的题 本题的题解在最下面↓↓↓ 不得不说,第一次接触类似于双栈排序的这种题,是在BZOJ的五月月赛上. [BZOJ4881][ ...