浏览器的一些事件,如:resize,scroll,keydown,keyup,keypress,mousemove等。这些事件触发频率太过频繁,绑定在这些事件上的回调函数会不停的被调用。这样浏览器的目的是为了保证信息的一致性,而对于我们来说就是一种资源的浪费了。
debounce的作用是在让在用户动作停止后延迟x ms再执行回调。
throttle的作用是在用户动作时没隔一定时间(如200ms)执行一次回调。
他们两个的共同点就是将多次回调的触发合并成一次执行。这就大大避免了过于频繁的事件回调操作。
本质就是将事件回调函数用debounce或throttle包装,事件触发的频率没有改变,只是我们自定义的回调函数的执行频率变低了。这个处理是基于DOM操作是十分巨大的开销。所以如果你的回调函数只是处理一些js的数据,那么用不用防抖和节流处理是一样的。
debounce实现
function debounce(fn,delay){
var delay=delay||200;
var timer;
return function(){
var th=this;
var args=arguments;
if (timer) {
clearTimeout(timer);
}
timer=setTimeout(function () {
timer=null;
fn.apply(th,args);
}, delay);
};
}
例子1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<style type="text/css">
#dom{
width: 200px; height: 200px; background: red
}
</style>
<body>
<div id="dom"></div>
</body>
<script type="text/javascript">
document.getElementById('dom').addEventListener("mousemove",debounce(function(){
console.log(this)
}));
</script>
</html>
例子2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div class="keycont">
输入:<input type="text" id="keybtn"/>
</div>
<div style="position:relative">
<div class="keysend" id="keyinfo"></div>
</div>
</body>
<script type="text/javascript">
var info=document.getElementById("keyinfo");
var btn=document.getElementById("keybtn");
btn.addEventListener("keydown",debounce(function(){
var div=document.createElement("div");
var str=new Date();
div.innerText=str
info.appendChild(div);
},500));
</script>
</html>
throttle实现
function throttle(fn,interval){
var last;
var timer;
var interval=interval||200;
return function(){
var th=this;
var args=arguments;
var now=+new Date();
if(last&&now-last<interval){
clearTimeout(timer);
timer=setTimeout(function(){
last=now;
fn.apply(th,args);
},interval);
}else{
last=now;
fn.apply(th,args);
}
}
}
window.addEventListener('scroll', throttle(function () {
console.log(11111)
}));
使用方法跟debounce一样。代码逻辑也类似。在触发时超过间隔时间interval ms则执行。否则不执行。if判断中的setTimeout是保证最后一次事件触发后能够调用,所以每次执行没到间隔时间时先清除timer,再重新启动timer。而在达到间隔时间时执行函数。代码逻辑也很简单,不用多说,相信聪明的你一看就能明白。
这个throttle节流的功能就是在固定的间隔时间执行回调函数,最常用的用处就是resize,scroll事件中处理。
requestAnimationFrame
上面介绍的抖动与节流实现的方式都是借助了定时器 setTimeout ,但是如果页面只需要兼容高版本浏览器或应用在移动端,又或者页面需要追求高精度的效果,那么可以使用浏览器的原生方法 requestAnimationFrame
window.requestAnimationFrame() 这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。这个方法接受一个函数为参,该函数会在重绘前调用。常用于 web 动画的制作,用于准确控制页面的帧刷新渲染,让动画效果更加流畅,当然它的作用不仅仅局限于动画制作,我们可以利用它的特性将它视为一个定时器。(当然它不是定时器)
通常来说,requestAnimationFrame 被调用的频率是每秒 60 次,也就是 1000/60 ,触发频率大概是 16.7ms 。(当执行复杂操作时,当它发现无法维持 60fps 的频率时,它会把频率降低到 30fps 来保持帧数的稳定。)
简单而言,使用 requestAnimationFrame 来触发滚动事件,相当于上面的:
throttle(fn,16.7)
总结
debounce用在keydown事件上验证用户名最好。而throttle用在resize改变布局上,onscroll滚动时候的。 requestAnimationFrame 可调节性十分差。但是相比 throttle(func, 16.7) ,用于更复杂的场景时,requestAnimationFrame 可能效果更佳,性能更好。