安排函數防抖與節流

前言

最近看到各類面經,防抖節流好像歷來沒有缺席過。雖然在項目中也使用過,但我對它倆的一直是javascript

此次必定要把它倆給安排的明明白白的java

防抖(Debounce)

概念

字面意思是防止抖動。在程序中就是爲了防止在必定時間內重複執行一段代碼(函數)。ajax

在函數被觸發n秒後再執行,若是在n秒內又有函數執行,則從新計算。後端

🌰

有一個輸入框,用戶輸入用戶名,而後向後端請求接口,獲取該用戶名是否存在。bash

通常的作法是當輸入框失焦時再去請求接口判斷。可是這樣體驗很差,若是是用戶輸入的內容實時的反饋結果,這樣有利於用戶體驗。閉包

當用戶輸入內容實時反饋結果代碼以下:app

// 輸入框
<input type="text" id="input"/>
let ipt = document.getElementById('input');

// 用戶輸入內容實時反饋結果
ipt.addEventListener('input',function(){
    let val = this.value;
    handleSendPhone(val);
});

// 請求接口
function handleSendPhone(val){
    ajaxRequest({
        user:val
    }).then(res => {
        console.log(res)
    })
}



//模擬數據
let items = ['abc','aaa','bbb','ccc','ddd'];
//模擬ajax請求
function ajaxRequest({user}){
    return new Promise((resolved,rejected) => {
        setTimeout(() => {
            let res = items.includes(user)?'正確':'錯誤';
            resolved(res);
        },500)
    });
}

複製代碼

上述gif圖中左側爲用戶輸入內容,右側爲請求接口。很明顯當用戶輸入內容時就會請求接口這種作法是不妥的,會形成資源上的浪費。函數

防抖的條件爲函數在頻繁執行時。恰好對應咱們上面的需求:頻繁請求接口。學習

那如今就輪到防抖上場了。ui

實現思路

給定函數執行時間間隔爲n,若 n 秒內沒有函數再次執行,則執行該函數。若 n 秒內函數再次執行,則從新計算函數被執行的時間。

初版:

利用setTimeout將傳入的函數延遲執行,在延遲執行到達以前,若是函數又被執行,則清除定時器,讓setTimeout從新計時。所以函數執行的條件爲,在setTimeout計時結束前,傳入的函數沒有被再次執行,這時傳入的函數就會執行。

/** * @fn : 要執行的函數 * @delay : 執行函數的時間間隔 */ 

function debounce(fn,delay){
	let timer; // 定時器
	return function(...args){ // 造成閉包
	    timer&&clearTimeout(timer); // 當函數再次執行時,清除定時器,讓定時器從新開始計時
	
	    // 利用定時器,讓指定函數延遲執行。
	    timer = setTimeout(function(){
	        // 執行傳入的指定函數
	        fn();
	    },delay)
	}
}

複製代碼

上述實現防抖函數並未實現傳參和this綁定。

第二版:

/**
* @fn : 要執行的函數
* @delay : 執行函數的時間間隔
*/ 

function debounce(fn,delay){
    let timer; // 定時器
    return function(...args){ // 造成閉包  外部執行的函數實際上是這個return出去的函數。
    
        // args 爲函數調用時傳的參數。
        
        let context = this; // this 爲函數執行時的this綁定。

        timer&&clearTimeout(timer); // 當函數再次執行時,清除定時器,讓定時器從新開始計時

        // 利用定時器,讓指定函數延遲執行。
        timer = setTimeout(function(){
            // 執行傳入的指定函數,利用apply更改this綁定和傳參
            fn.apply(context,args);
        },delay)
    }
}
複製代碼

使用

// 輸入框
<input type="text" id="input"/>
let ipt = document.getElementById('input');

let handler = debounce(handleSendPhone,500);
//handler:debounce執行後return的函數。

ipt.addEventListener('input',function(){
    let val = this.value;
    handler(val);
});

// 請求接口
function handleSendPhone(val){
    ajaxRequest({
        user:val
    }).then(res => {
        console.log(res)
    })
}

複製代碼

對比效果

上述對比中,加入防抖後的代碼,在連續的輸入內容時,並不會連續執行請求,而是在上次輸入和下次輸入間隔必定的時間纔會執行請求。 這樣對比就很明顯了,在不影響業務需求的狀況下,防抖能夠避免資源浪費。

當即執行版

經過上述防抖後的gif圖咱們能夠看到,當用戶連續輸入內容時並不會返回結果,而是要知足咱們的執行間隔時纔會執行。

這樣的效果若是有細(刁)心(鑽)的產品的話,他應該不但願是這樣,他會但願當用戶輸入時當即請求接口反饋結果,而後再等到輸入間隔知足咱們的間隔時間時再執行。

思路:

那就須要咱們上面的防抖函數在調用時就執行一次。相比較剛纔的防抖,當即執行版防抖只是多了一步當即執行。

代碼實現:

/**
* @fn : 要執行的函數
* @delay : 執行函數的時間間隔
* @immediate : 是否當即執行函數 true 表當即執行,false 表非當即執行
*/        

function debounce(fn,delay,immediate){

    let timer; // 定時器
 
    return function(...args){ // 造成閉包  外部執行的函數實際上是這個return出去的函數。
        
        // args 爲函數調用時傳的參數。
        
        let context = this; // this 爲函數執行時的this綁定。

        timer&&clearTimeout(timer); // 當函數再次執行時,清除定時器,讓定時器從新開始計時

        // immediate爲true 表示第一次觸發就執行
        if(immediate){
            // 執行一次以後賦值爲false  
            immediate = false;
            fn.apply(context, args)
        }
        // 利用定時器,讓指定函數延遲執行。
        timer = setTimeout(function(){
            // immediate 賦值爲true  下次輸入時 仍是會當即執行
            immediate = true;
            // 執行傳入的指定函數,利用apply更改傳入函數內部的this綁定,傳入 args參數
            fn.apply(context,args);
        },delay)
    }
}

複製代碼

對比效果:

其實防抖很好理解。好比說當咱們乘坐電梯時,當有人進入電梯時,電梯門會開,而後倒計時關上門。當持續不斷的人進入電梯時,電梯就不會過指定的時間關上門。而是當有一我的進入時,電梯開始倒計時關門時間,再有一我的進入時,重置倒計時。直到倒計時完沒有人進入電梯,則關門。這裏就是咱們所說的防抖。這裏持續不斷的人就是頻繁觸發的函數,這裏的關門就是咱們最後要執行的函數。倒計時關門時間就是咱們要執行函數的間隔。

節流(Throttle)

概念

連續觸發函數時,在規定單位時間內只會觸發一次。

🌰

產品經理向你走了過來...... : 剛纔的效果我還不滿意,再改改。我:您說改爲什麼樣(內心:!@#$%^&%)。產品經理:剛纔的效果,若是用戶一直連續輸入必需要等不輸入時才能獲得結果。如今改爲用戶每輸入幾個字符時就請求一次接口。

上面說了節流就是函數連續觸發時,在規定的時間間隔內就會觸發一次。當用戶連續輸入時,每過n秒,請求一次接口。變相的至關於用戶每輸入幾個字符就請求一次接口。

實現思路

若是函數持續觸發,則讓函數延遲執行,若是在延遲執行期間,函數還在觸發,則無效。直到函數延遲執行結束,方可進行下一次函數延遲執行。

/** * @fn : 要執行的函數 * @delay : 每次函數的時間間隔 */  
function throttle(fn,delay){
    let timer;    // 定時器

    return function(...args){
        let context = this;
        // 若是timer存在,說明函數還未該執行 也就是距離上次函數執行未間隔指定的時間
        if(timer) return;
        // 若是函數執行以後還有函數還在觸發,再延遲執行。
        timer = setTimeout(function(...args){
            // 當函數執行時,讓timer爲null。
            timer = null;
            fn.apply(context,args);
        },delay);
    }
}

複製代碼

使用

<input type="text" id="input"/>

let ipt = document.getElementById('input');

let handler = throttle(handleSendPhone,1000);

ipt.addEventListener('input',function(){
    let val = this.value;
    handler(val);
});

// 請求接口
function handleSendPhone(val){
    ajaxRequest({
        user:val
    }).then(res => {
        console.log(`請求結果爲:${res}`)
    })
}
複製代碼

對比效果:

總結

  • 防抖和節流在必定程度上類似,都是爲了節省資源。提高用戶體驗。
  • 防抖是在函數觸發時延遲指定的時間執行,如在延遲過程當中函數又被觸發,則從新計算執行時間。
  • 節流是在函數連續觸發時,在規定的時間內只會執行一次。
  • 直白一點就是防抖控制次數,節流控制頻率

應用

  • 防抖

    根據輸入框的內容來請求接口,返回結果。若是實時請求接口就會形成資源上的浪費。就須要假設用戶輸入完成時, 再去請求接口,好比設置時間間隔爲1s,當用戶在間隔1s後還未輸入,則認爲他是輸入完成,當1s到達以前又輸入了字符,則從新間隔1s請求接口。

  • 節流

    在一個頁面中實時計算某個元素到窗口頂部的距離時,須要監聽滾動條變化,而後計算元素座標而後處理一些邏輯。監聽滾動條變化時不用實時去計算元素位置,而是利用節流,好比300ms計算一次,這樣能夠避免頻繁執行一些代碼。還能夠避免使用了offsetHeight,引發過多的重繪。

##最後

若是文中有錯誤,請務必留言指正,萬分感謝。

點個贊哦,讓咱們共同窗習,共同進步。

相關文章
相關標籤/搜索