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


Posted in Javascript onAugust 11, 2016

函数节流,简单地讲,就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。

函数节流的原理挺简单的,估计大家都想到了,那就是定时器。当我触发一个时间时,先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 >= 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 && options.leading === false) previous = now; 
// 这里引入了一个remaining的概念:还剩多长时间执行事件 
var remaining = wait - (now - previous); 
context = this; 
args = arguments; 
// remaining <= 0 考虑到事件停止后重新触发或者 
// 正好相差wait的时候,这些情况下,会立即触发事件 
// remaining > wait 没有考虑到相应场景 
// 因为now-previous永远都是正值,且不为0,那么 
// remaining就会一直比wait小,没有大于wait的情况 
// 估计是保险起见吧,这种情况也是立即执行 
if (remaining <= 0 || remaining > wait) 
{ 
if (timeout)
{ 
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; 
};};

由上可见,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),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
Convert Seconds To Hours
Jun 16 Javascript
jQuery调用WebService的实现代码
Jun 19 Javascript
Javascript 加载和执行-性能提高篇
Dec 28 Javascript
JS实现文字向下滚动完整实例
Feb 06 Javascript
EasyUi datagrid 实现表格分页
Feb 10 Javascript
node实现的爬虫功能示例
May 04 Javascript
IE8中jQuery.load()加载页面不显示的原因
Nov 15 jQuery
爬虫利器Puppeteer实战
Jan 09 Javascript
vue element-ui table组件动态生成表头和数据并修改单元格格式 父子组件通信
Aug 15 Javascript
layui原生表单验证的实例
Sep 09 Javascript
深入详解JS函数的柯里化
Jun 09 Javascript
关于vue-router-link选择样式设置
Apr 30 Vue.js
JavaScript性能优化总结之加载与执行
Aug 11 #Javascript
js接收并转化Java中的数组对象的方法
Aug 11 #Javascript
Js调用Java方法并互相传参的简单实例
Aug 11 #Javascript
JS中的hasOwnProperty()、propertyIsEnumerable()和isPrototypeOf()
Aug 11 #Javascript
基于js对象,操作属性、方法详解
Aug 11 #Javascript
JS中的hasOwnProperty()和isPrototypeOf()属性实例详解
Aug 11 #Javascript
Node.js中防止错误导致的进程阻塞的方法
Aug 11 #Javascript
You might like
第九节--绑定
2006/11/16 PHP
Discuz 5.0 中读取纯真IP数据库函数分析
2007/03/16 PHP
php删除指定目录的方法
2015/04/03 PHP
Laravel框架实现多数据库连接操作详解
2019/07/12 PHP
javascript实现日历控件(年月日关闭按钮)
2012/12/12 Javascript
使用jQuery插件创建常规模态窗口登陆效果
2013/08/23 Javascript
Node.js中的模块机制学习笔记
2014/11/04 Javascript
JS实现单行文字不间断向上滚动的方法
2015/01/29 Javascript
完善的jquery处理机制
2016/02/21 Javascript
Sea.JS知识总结
2016/05/05 Javascript
基于JavaScript实现本地图片预览
2017/02/08 Javascript
简单实现AngularJS轮播图效果
2020/04/10 Javascript
jQuery实现的页面弹幕效果【测试可用】
2018/08/17 jQuery
javascript 设计模式之组合模式原理与应用详解
2020/04/08 Javascript
使用nodejs实现JSON文件自动转Excel的工具(推荐)
2020/06/24 NodeJs
[50:28]LGD女子学院第三期 DOTA2复仇之魂教学
2013/12/24 DOTA
[01:59]翻天覆地,因你而变,7.20版本地图更新速览
2018/11/24 DOTA
使用相同的Apache实例来运行Django和Media文件
2015/07/22 Python
flask中过滤器的使用详解
2018/08/01 Python
python实现字符串加密 生成唯一固定长度字符串
2019/03/22 Python
Pyqt清空某一个QTreeewidgetItem下的所有分支方法
2019/06/17 Python
解决pyecharts运行后产生的html文件用浏览器打开空白
2020/03/11 Python
详解python tkinter 图片插入问题
2020/09/03 Python
Python远程linux执行命令实现
2020/11/11 Python
Python基于unittest实现测试用例执行
2020/11/25 Python
python如何构建mock接口服务
2021/01/28 Python
Python的collections模块真的很好用
2021/03/01 Python
利用CSS3的flexbox实现水平垂直居中与三列等高布局
2016/09/12 HTML / CSS
纽约家具、家居装饰和地毯店:ABC Carpet & Home
2017/06/21 全球购物
澳大利亚汽车零部件、音响及配件超市:Automotive Superstore
2018/06/19 全球购物
2014年社区植树节活动方案
2014/02/28 职场文书
最新优秀教师个人先进事迹材料
2014/05/06 职场文书
党员十八大心得体会
2014/09/12 职场文书
工作作风整顿个人剖析材料
2014/10/11 职场文书
班主任自我评价范文
2015/03/11 职场文书
教师创先争优承诺书
2015/04/27 职场文书