一、背景
防抖和节流是两种不同的控制一个函数执行次数的方法,其目的都是为了节约计算机资源。
当我们操作DOM的时候,加上节流或者防抖就非常有必要,因为众所周知,操作DOM的开销是非常大的,所以要尽可能减少DOM操作次数。
看下面的在线例子:
https://codepen.io/dcorb/pen/PZOZgB
当鼠标滚动或者拖拽的时候可以轻易地每秒触发30个事件,而且在移动端慢速滚动可以达到每秒100次,要是这么短的时间内每次都触发一个或多个函数,那浏览器应该会卡死崩溃的,所以这就引出了防抖和节流。
二、防抖(Debounce)
防抖允许我们把多次连续的调用放到一次里面去执行,在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
试想一下,你正身处一部电梯之中,电梯门开始关闭,然后突然又来了一个人,按下了开门键,电梯门开了,他顺利上了电梯,然后电梯又开始缓慢关闭,但是此时又匆匆跑来了另一个人,电梯门又开了,此时,快要迟到的你心里面开始有点不满了。但是,这其实就是一个防抖的实际例子,电梯尽可能的使其资源利用率达到最高,在尽可能的情况下,载上尽可能多的人。
下面是一个例子:
https://codepen.io/dcorb/pen/KVxGqN
- 具体实现
function debounce(func,delay) {
let timer;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this,arguments);
},delay)
}
}
Lodash中实现 _.debounce
和 _.throttle
的功能很全面,可以直接使用,其中的 throttle
函数是使用 _.debounce
新增 maxWait` 选项来实现的, 有兴趣可以自行查看 源码。
- Resize实例
// Based on http://www.paulirish.com/2009/throttled-smartresize-jquery-event-handler/
$(document).ready(function(){
var $win = $(window);
var $left_panel = $('.left-panel');
var $right_panel = $('.right-panel');
function display_info($div) {
$div.append($win.width() + ' x ' + $win.height() + '<br>');
}
$(window).on('resize', function(){
display_info($left_panel);
});
$(window).on('resize', _.debounce(function() {
display_info($right_panel);
}, 400));
});
可以用来监听浏览器窗口尺寸变化事件。
https://codepen.io/dcorb/pen/XXPjpd
- 输入验证实例
$(document).ready(function(){
var $statusKey = $('.status-key');
var $statusAjax = $('.status-ajax');
var intervalId;
// Fake ajax request. Just for demo
function make_ajax_request(e){
var that = this;
$statusAjax.html('That\'s enough waiting. Making now the ajax request');
intervalId = setTimeout(function(){
$statusKey.html('Type here. I will detect when you stop typing');
$statusAjax.html('');
$(that).val(''); // empty field
},2000);
}
// Event handlers to show information when events are being emitted
$('.autocomplete')
.on('keydown', function (){
$statusKey.html('Waiting for more keystrokes... ');
clearInterval(intervalId);
})
// Display when the ajax request will happen (after user stops typing)
// Exagerated value of 1.2 seconds for demo purposes, but in a real example would be better from 50ms to 200ms
$('.autocomplete').on('keydown',
_.debounce(make_ajax_request, 1300));
});
当用户输入并且发起Ajax
请求的时候,_.debounce
可以用来实现防抖,比如间隔2秒未检测到用户输入才发起请求。
三、节流(Throttle)
- 具体实现
使用定时器
var throttle = function(func, delay) {
var timer = null;
return function() {
if (!timer) {
timer = setTimeout(function() {
func.apply(this,arguments);
timer = null;
},delay);
}
}
}
另一种定时器写法:
let isAllow = true;
function throttle() {
let fun = function() {
if (!isAllow)
return;
let timer = setTimeout(() => {
console.log("throttle");
clearTimeout(timer);
timer = null;
isAllow = true;
},1000);
};
fun();
}
二者的区别是后者使用了isAllow标志位来判断是否需要执行函数。
- 滚动条实例
一个十分常见的例子,当用户的鼠标滚动的时候,检查鼠标位置离底部的距离,当接近底部时,发起Ajax
请求。
https://codepen.io/dcorb/pen/eJLMxa
requestAnimationFrame
requestAnimationFrame
是另一种限制函数执行次数的方式,可以认为与_.throttle
类似,但是其拥有更高的准确度,因为其本身就是为了更好的精确度而生的原生API。
requestAnimationFrame的优点
- 致力于60fps(16ms每帧),自己可以决定最好的渲染时间。
- 简单且标准的原生API,不太容易改变,减少维护成本。
requestAnimationFrame的缺点
- 需要手动开始或取消rAFs,
.debounce
或.throttle
则不需要。 - 如果tab没有激活,则不会执行,即使触发 Ascroll, mouse 或者 keyboard 等事件也不行.
- 不支持 IE9, Opera Mini 和 老版本 Android.
- 不支持
node.js
,所以不能在服务端使用。
requestAnimationFrame实例
与_.throttle
相比,同时设置 16ms, 相同的性能环境下,rAF 可以在更复杂的情况下拥有更好的结果。
https://codepen.io/dcorb/pen/pgOKKw
四、结论
一般来说,如果你的JavaScript函数是直接绘制或者动画某些属性,那么可以使用
requestAnimationFrame
,在需要重新计算元素位置的地方使用。需要发起
Ajax
请求,或者决定是否增加或删除一个类的时候,可以考虑使用_.debounce
或者_.throttle
。debounce: 在事件被触发x秒后再执行回调,如果在这x秒内又被触发,则重新计时。
throttle: 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
requestAnimationFrame: 节流的另外一种选项,在函数重新计算或者渲染元素时,需要平滑过渡或者动画的时候使用。
五、参考
https://css-tricks.com/debouncing-throttling-explained-examples/