函数节流,简单地讲,就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。
函数节流的原理挺简单的,估计大家都想到了,那就是定时器。当我触发一个时间时,先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 "htmlcode">_.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),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!