省政府網(wǎng)站建設(shè)標(biāo)準(zhǔn)營銷網(wǎng)站定制公司
防抖與節(jié)流
1.防抖
1.1 為什么要防抖?
在項目中,有的操作是高頻觸發(fā)的,但是其實觸發(fā)一次就好了,比如我們短時間內(nèi)多次縮放頁面,那么我們不應(yīng)該每次縮放都去執(zhí)行操作,應(yīng)該只做一次就好。再比如說監(jiān)聽輸入框的輸入,不應(yīng)該每次都去觸發(fā)監(jiān)聽,應(yīng)該是用戶完成一段輸入后再進行觸發(fā)。
所以,防抖就是防止抖動,避免事件的重復(fù)觸發(fā)。
思路:等待用戶高頻操作完成后,再完成操作。
1.2 基礎(chǔ)防抖如何實現(xiàn)?
基礎(chǔ)設(shè)計思路:事件觸發(fā)后,開啟一個定時器,當(dāng)該定時器到時間時,觸發(fā)操作;而如果事件在該定時器限定的時間內(nèi)再次觸發(fā),則清除當(dāng)前定時器,并再次開啟一個新的定時器。
代碼如下:
// fn --- 要執(zhí)行的操作,delay --- 延遲時間,在該時間內(nèi)用戶沒有再次觸發(fā)則執(zhí)行操作
function debounce(fn, delay){// timer --- 定時器的名稱,用于清除定時器let timer = null;return function(arguments){// 實際進行防抖的部分// 如果再次觸發(fā),首先清除上一次的定時器clearTimeout(timer);// 創(chuàng)建一個新定時器并記錄名稱timer = setTimeout(()=> {// 用apply方法執(zhí)行目標(biāo)函數(shù)fn.apply(this, arguments);}, delay)}
}
實際上,可以通過下面這張圖來理解基礎(chǔ)的防抖:
2.節(jié)流
2.1 為什么要節(jié)流?
基礎(chǔ)的防抖存在一個問題,事件會一直等到用戶完成操作后一段時間在操作,如果一直操作,會一直不觸發(fā)。比如說是一個按鈕,點擊就發(fā)送請求,如果一直點,那么請求就會一直發(fā)布出去。這里正確的思路應(yīng)該是第一次點擊就發(fā)送,然后上一個請求回來后,才能再發(fā),即節(jié)流。
節(jié)流就是減少流量,將頻繁觸發(fā)的事件減少,并每隔一段時間執(zhí)行。即,控制事件觸發(fā)的頻率。
思路:某個操作希望上一次的完成后再進行下一次,或者希望隔一段時間觸發(fā)一次
2.2 如何實現(xiàn)基礎(chǔ)節(jié)流?
基礎(chǔ)設(shè)計思路:我們可以設(shè)計一種類似控制閥門一樣定期開放的函數(shù),事件觸發(fā)時讓函數(shù)執(zhí)行一次,然后關(guān)閉這個閥門,過了一段時間后再將這個閥門打開,再次觸發(fā)事件。
代碼如下:
// fn --- 要執(zhí)行的操作,delay --- 延遲時間,在該時間內(nèi)操作最多只會執(zhí)行一次
function throttle(fn, delay){// 閥門是否開啟let valid = true;return function(arguments){if(valid) { //如果閥門已經(jīng)打開,就繼續(xù)往下,設(shè)定定時器,指明在一定延遲時間后執(zhí)行一次操作// 此時已經(jīng)確定會執(zhí)行一次操作,因此關(guān)閉閥門valid = false;setTimeout(()=> {fn.apply(this, arguments);//定時器結(jié)束后執(zhí)行valid = true;//執(zhí)行完成后打開閥門}, delay)}}
}
實際上,可以通過下面這張圖來理解基礎(chǔ)的節(jié)流:
3.防抖節(jié)流的應(yīng)用場景、聯(lián)系以及區(qū)別
防抖
- search搜索聯(lián)想,用戶在不斷輸入值時,用防抖來節(jié)約請求資源
- window觸發(fā)resize的時候,不斷的調(diào)整瀏覽器窗口大小會不斷的觸發(fā)這個事件,用防抖來讓其只觸發(fā)一次
節(jié)流
- 鼠標(biāo)不斷點擊觸發(fā),mousedown(單位時間內(nèi)只觸發(fā)一次)
- 監(jiān)聽滾動事件,比如是否滑到底部自動加載更多
聯(lián)系與區(qū)別
防抖節(jié)流的共同點在于:都是為了阻止操作高頻觸發(fā),從而浪費性能。
兩者的不同點在于:
- 防抖是觸發(fā)高頻事件后n秒內(nèi)函數(shù)只會執(zhí)行一次,如果n秒內(nèi)高頻事件再次被觸發(fā),則重新計時。適用于可以多次觸發(fā)但觸發(fā)只生效最后一次的場景,可能會出現(xiàn)操作始終不執(zhí)行的情況。
- 而節(jié)流則不同,事件高頻觸發(fā),無論觸發(fā)多少次,在一定時間內(nèi)只會執(zhí)行一次,即定期執(zhí)行。相比于防抖,節(jié)流的響應(yīng)更加平滑,不會出現(xiàn)始終不執(zhí)行操作的情況!
4.防抖與節(jié)流的優(yōu)化
對于上面的基礎(chǔ)防抖與節(jié)流的方法,主要有兩個可以優(yōu)化的方面:
- 復(fù)用性優(yōu)化:對于實際項目中,我們不可能在每次需要進行防抖節(jié)流的時候,都重新寫一遍代碼,因此,我們需要將防抖節(jié)流的方法封裝為內(nèi)部API提高復(fù)用性!
- 功能方面的優(yōu)化:
- 防抖:添加立即執(zhí)行選項解決可能始終不執(zhí)行的問題;延遲防抖,解決高頻設(shè)定定時器的問題。
- 節(jié)流:可能需要點擊后立即執(zhí)行等等。
立即執(zhí)行
在某些業(yè)務(wù)場景中,使用防抖節(jié)流時,基礎(chǔ)的防抖節(jié)流可能會導(dǎo)致響應(yīng)時間變長,這就會影響到用戶的使用體驗,因此,需要在觸發(fā)事件的時候,立即執(zhí)行處理函數(shù),但后續(xù)也能起到防抖節(jié)流的作用。
立即執(zhí)行的防抖和節(jié)流,其原理是一致的,即:添加一個計數(shù)器或立即執(zhí)行的標(biāo)識。
實現(xiàn)如下:
// 防抖
export const debounce = (fun , wait=1000) => {let timeout = nulllet count = 0return function(){let _this = thislet arg = argumentsif(timeout){//如果存在定時器就清空clearTimeout(timeout)}if (!count) {// 第一次點擊時立即執(zhí)行count++fun.apply(_this, arg)timeout = setTimeout(() => {count = 0}, wait)} else {count++timeout = setTimeout(() => {fun.apply(_this, arg)count = 0}, wait)}}
}// 節(jié)流
export const throttled = (fun, wait=1000, immediate) => {let preTime = 0;let timerId;return function() {let _this = this;let args = arguments;if(immediate) { // immediate 為true 時立即執(zhí)行let nowTime = Date.now();if(nowTime - preTime > wait) {fun.apply(_this, args);preTime = nowTime;}} else {if(!timerId) {timerId = setTimeout(function() {fun.apply(_this, args);timerId = null;}, wait);}}}
}
其他優(yōu)化
在不同的業(yè)務(wù)場景中,對于防抖節(jié)流也有不同的需求,如果單純地為了每一個場景編寫防抖節(jié)流函數(shù),是相當(dāng)費事且麻煩的工作,因此,不如使用別人封裝好的庫。
參考:http://t.csdn.cn/RVzMK
最終解決方案:
underscore.js 庫中的 _.throttle()
和 _.debounce()
— 參考https://www.likecs.com/show-307738657.html