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 相关文章推荐
Javascript 获取字符串字节数的多种方法
Jun 02 Javascript
js实现运动logo图片效果及运动元素对象sportBox使用方法
Dec 25 Javascript
JQuery 中几个类选择器的简单使用介绍
Mar 14 Javascript
jQuery .attr()和.removeAttr()方法操作元素属性示例
Jul 16 Javascript
JavaScript中日期的相关操作方法总结
Oct 24 Javascript
使用node+vue.js实现SPA应用
Jan 28 Javascript
基于JS实现Android,iOS一个手势动画效果
Apr 27 Javascript
jQuery Mobile动态刷新页面样式的实现方法
May 28 Javascript
手动初始化Angular的模块与控制器
Dec 26 Javascript
SVG描边动画
Feb 23 Javascript
整理一些最近经常遇到的前端面试题
Apr 25 Javascript
JavaScript展开操作符(Spread operator)详解
Jul 20 Javascript
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
php 前一天或后一天的日期
2008/06/28 PHP
PHP学习之整理字符串
2011/04/17 PHP
php数组函数序列之ksort()对数组的元素键名进行升序排序,保持索引关系
2011/11/02 PHP
Fatal error: session_start(): Failed to initialize storage module: files问题解决方法
2014/05/04 PHP
基于PHP实现假装商品限时抢购繁忙的效果
2015/10/16 PHP
PHP strip_tags() 去字符串中的 HTML、XML 以及 PHP 标签的函数
2016/05/22 PHP
PHP实现批量检测网站是否能够正常打开的方法
2016/08/23 PHP
PHP解压ZIP文件到指定文件夹的方法
2016/11/17 PHP
php将从数据库中获得的数据转换成json格式并输出的方法
2018/08/21 PHP
PHP时间函数使用详解
2019/03/21 PHP
jquery中ajax学习笔记3
2011/10/16 Javascript
JS实现仿Windows7风格的网页右键菜单效果代码
2015/09/11 Javascript
jQuery实现仿腾讯迷你首页选项卡效果代码
2015/09/17 Javascript
django js 实现表格动态标序号的实例代码
2019/07/12 Javascript
解决axios post 后端无法接收数据的问题
2019/10/29 Javascript
React中使用Vditor自定义图片详解
2020/12/25 Javascript
[15:09]DOTA2国际邀请赛采访专栏:Loda
2013/08/06 DOTA
python启动办公软件进程(word、excel、ppt、以及wps的et、wps、wpp)
2009/04/09 Python
在Python的while循环中使用else以及循环嵌套的用法
2015/10/14 Python
python实现装饰器、描述符
2018/02/28 Python
Django开发中复选框用法示例
2018/03/20 Python
用Python爬取QQ音乐评论并制成词云图的实例
2019/08/24 Python
一款CSS3实现多功能下拉菜单(带分享按)的教程
2014/11/05 HTML / CSS
送餐员岗位职责范本
2014/02/21 职场文书
中医学专业自荐信范文
2014/04/01 职场文书
应届生求职自荐信范文
2014/04/07 职场文书
2014幼儿园教师师德师风演讲稿
2014/09/10 职场文书
依法行政工作汇报材料
2014/10/28 职场文书
南湾猴岛导游词
2015/02/09 职场文书
劳动保障个人工作总结
2015/03/04 职场文书
小学班主任工作总结2015
2015/04/07 职场文书
毕业生自我鉴定范文
2019/05/13 职场文书
Python爬虫之爬取哔哩哔哩热门视频排行榜
2021/04/28 Python
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
2022/03/16 Redis
Nginx中使用Lua脚本与图片的缩略图处理的实现
2022/03/18 Servers
Java+swing实现抖音上的表白程序详解
2022/06/25 Java/Android