函数节流和函数防抖,两者都是优化高频率执行js代码的一种手段。在一定时间内,代码执行的次数不一定要非常多。达到一定频率就足够了。因为跑得越多,带来的效果也是一样。倒不如,把js代码的执行次数控制在合理的范围。既能节省浏览器CPU资源,又能让页面浏览更加顺畅,不会因为js的执行而发生卡顿。
目标
学习思想。
函数防抖
来源:函数防抖
原理
你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行。
核心代码
function debounce(func, wait) {
var timeout;
return function () {
clearTimeout(timeout)
timeout = setTimeout(func, wait);
}
}
上述代码看似没有问题,但是有如下几个点是需要注意的(使用了debounce之后):
- this指向变了
- 无法获取event对象
- 立即执行:我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
- 原事件handler函数有返回值呢?
- 取消,每次都要停止n秒之后才能触发执行,如果希望取消呢?
最终代码
function debounce(func, wait, immediate) {
//immediate表示是否立即执行
var timeout, result;
var debounced = function () {
var context = this;// 修复this指向
var args = arguments;//修复event对象
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
// 这里设计很巧妙,通过判断定时器来是否立即执行,定时器内部修改自身指向
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
函数节流
来源:函数节流
原理
- 如果你持续触发事件,每隔一段时间,只执行一次事件。
- 根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。 我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。
- 有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。
核心代码
时间戳实现
function throttle(func, wait) { var context, args; var previous = 0; return function() { var now = +new Date(); context = this; args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } } }
特点:首次触发立即执行,事件不触发立即不执行
定时器实现
function throttle(func, wait) { var timeout; return function() { context = this; args = arguments; if (!timeout) { timeout = setTimeout(function(){ timeout = null; func.apply(context, args) }, wait) } } }
特点:首次触发指定时间之后才执行,停止触发之后会在执行一次
最终代码
从核心代码可以看出各有特点,那么如何结合两者的特点且灵活做到可配置呢?
- leading:false 表示禁用第一次执行
- trailing: false 表示禁用停止触发的回调
- leading:false 和 trailing: false 不能同时设置。原因自己理解!
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
//如果不禁用第一次执行,previous赋值
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = new Date().getTime();
//如果禁用第一次执行,直接设置previous = now,分支不会进入,使用定时器的方式
if (!previous && options.leading === false) previous = now;
//下次触发 func 剩余的时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果没有剩余的时间了或者你改了系统时间
if (remaining <= 0 || remaining > wait) {
//进入了这里,直接取消定时器,防止重复执行
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
return throttled;
}
总结
这是追加的内容,在之前很长的一段时间内,我无法区分这两个概念,也就是说把实现代码放在我面前,我只会知道这是两者之一,知道今天看到一篇博客,就真的区分啦。
函数防抖,debounce。其概念其实是从机械开关和继电器的“去弹跳”(debounce)衍生 出来的,基本思路就是把多个信号合并为一个信号。
单反也有相似的概念,在拍照的时候手如果拿不稳晃的时候拍照一般手机是拍不出好照片的,因此智能手机是在你按一下时连续拍许多张, 能过合成手段,生成一张。翻译成JS就是,事件内的N个动作会变忽略,只有事件后由程序触发
的动作只是有效。
函数防抖使用场景:比如input输入格式验证,如果需要进行远程验证,如果用户每键入一个字符就发送一个请求的话,无意会加大服务端开销。这时候就可以使用防抖。
函数节流,throttle。节流的概念可以想象一下水坝,你建了水坝在河道中,不能让水流动不了,你只能让水流慢些。换言之,你不能让用户的方法都不执行。如果这样干,就是debounce了。为了让用户的方法在某个时间段内只执行一次,我们需要保存上次执行的时间点与定时器。
函数节流会用在比input, keyup更频繁触发的事件中,如resize, touchmove, mousemove, scroll。throttle 会强制函数以固定的速率执行。因此这个方法比较适合应用于动画相关的场景。