1. 程式人生 > >JS事件中防抖debounce和節流throttle以及requestAnimationFrame

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 可能效果更佳,效能更好。