函数节流、函数防抖实现原理分析

之前翻译了一篇博客,里面有讲到这个,今天单独拎出来聊聊。

前言

事件的触发权很多时候都属于用户,有些情况下会产生问题:

  • 向后台发送数据,用户频繁触发,对服务器造成压力

  • 一些浏览器事件:window.onresizemousemove等,触发的频率非常高,会造成浏览器性能问题

如果你碰到这些问题,那就需要用到这些技术了。

我们先来解释一下函数节流(Throttling)和函数防抖(Debouncing)的区别:

我们上班、生活每天都需要坐电梯,用这个比喻再恰当不过了:

函数防抖和我们平时坐电梯差不多,如果有人进电梯(用户触发事件),那将在10秒钟后出发(执行程序),这时如果又有人进电梯了(用户在10秒内再次触发事件),我们又得等10秒再出发(重新计时)。

函数节流就比较直观了,有人进电梯,就开始计时,每10秒运送一次,如果没有人,则待机。

这两种策略具体使用场景还得看你的实际需求了,但是,只要理解了这个思想,接下来的就好办了。

函数节流(Throttling)

函数节流的作用上面讲的很清晰了,接下来我们分析一下如何实现它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var throttle = function(fn, interval) { //fn为要执行的函数,interval为延迟时间
var _self = fn, //保存需要被延迟执行的函数引用
timer, //定时器
firstTime = true; //是否第一次调用
return function() { //返回一个函数,形成闭包,持久化变量
var args = arguments, //缓存变量
_me = this;
if(firstTime) { //如果是第一次调用,不用延迟执行
_self.apply(_me, args);
return firstTime = false;
}
if(timer) { //如果定时器还在,说明上一次延迟执行还没有完成
return false;
}
timer = setTimeout(function() { //延迟一段时间执行
clearTimeout(timer);
timer = null;
_self.apply(_me, args);
}, interval || 500);
};
};
//使用
window.onresize = throttle(function() {
//你要执行的代码
}, 500);

其实函数节流和函数防抖的关键就是对setTimeout的运用,说个题外话,当你对setTimeoutsetInterval内部的运作原理彻底了解后,你就是一名JS大神了。😝

函数防抖(Debouncing)

我个人在开发中比较喜欢使用函数防抖策略,其实也说不上谁好,适应的场景不同。

我把注解也写在代码中了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function debounce(fn, interval, immediate) {
//fn为要执行的函数
//interval为等待的时间
//immediate判断是否立即执行
var timeout; //定时器
return function() { //返回一个闭包
var context = this, args = arguments; //先把变量缓存
var later = function() { //把稍后要执行的代码封装起来
timeout = null; //成功调用后清除定时器
if(!immediate) fn.apply(context, args); //不立即执行时才可以调用
};
var callNow = immediate && !timeout; //判断是否立即调用,并且如果定时器存在,则不立即调用
clearTimeout(timeout); //不管什么情况,先清除定时器,这是最稳妥的
timeout = setTimeout(later, interval); //延迟执行
if(callNow) fn.apply(context, args); //如果是第一次触发,并且immediate为true,则立即执行
};
};
//使用
var myEfficientFn = debounce(function() {
//你要做的事
}, 250);
window.addEventListener('resize', myEfficientFn);

上面代码有一个巧妙的设计:var callNow = immediate && !timeout;,判断了timeout,如果存在,说明有定时器在运行,那就不是第一次执行,则不执行if(callNow)里的代码了。

总结

这两个代码块是在开发中经常使用的,无论是为了适应需求还是优化性能,我们都没有理由不适用它们。

另外在jQuery的源码中也使用了这种技巧,即便在这个框架横行的时代,还是只有底层的知识能让我感到踏实。

本文作者:余震(Freak)
本文出处:Rockjins Blog
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN许可协议。转载请注明出处!

坚持,您的支持将鼓励我继续爬下去!