JS事件中防抖debounce和節流throttle以及requestAnimationFrame
瀏覽器的一些事件,如: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 可能效果更佳,效能更好。