引入
上篇我們講了防抖
,這篇我們就談談防抖
的好兄弟 – 節流
。這里在老生常談般的提一下他們兩者之間的區別,順帶給讀者鞏固下。
PS:
開源節流
中節流與這個技術上的節流
,個人認為本質上是一樣的。
- 開源節流的節流指的是節省公司的金錢開支。
- 前端技術上的節流指的是稀釋函數的調用頻率,節省CPU的開支。
區別
節流
:N
秒內只運行一次,若在N
秒內重復觸發,只有第一次生效防抖
:N
秒后在執行該事件,若在N
秒內被重復觸發,則重新計時
不過我認為還是防抖那篇文章有個讀者的評論更顯生動 🐶, 在此對該讀者表示感謝🙏。
節流
: 可以看做攻擊間隔,點的再快沒打出來也不會同時攻擊兩次。防抖
: 可以理解為回城,每點一下就要重新跑.
節流例子
這里我舉兩個常見的🌰,大家有什么更好的🌰可以在評論里回復
這個王者榮耀的攻擊例子也算是節流,不過這個我就不舉了。🐶
生活例子
這里跟大家舉一個生活例子,更顯生動。
我們知道一般女神回復舔🐶的概率都是比較低的,假設女神每天回復舔🐶一次。也就是說如果今天女生已經回復過了,那么無論舔🐶發再多的信息對方也是不會回的。今天的次數已經消耗完畢了,也只能等明天才能刷新。
短信驗證
我們知道短信驗證是在生活中很常見,其實短信驗證便用到了節流的技術。
因為發短信其實是要錢的,為了避免一個用戶重復點擊導致發出多條短信就要使用節流。比如獲取一次驗證碼的有效時間為60
秒,則60
秒內這個發送短信的方法不能再次觸發。
手撕代碼
xdm,接下來開始手撕代碼了。這里有兩種實現方式,分別是
時間戳
實現和定時器
實現。
時間戳實現
原理
每次事件觸發都會檢查距離上次執行的時間間隔,如果超過指定的等待時間delay
,則執行函數。并且更新上一次執行時間為為當前的時間戳。
代碼
function throttleByTimestamp(fn, wait) {let lastTime = 0; // 用于保存上一次執行的時間戳return function () {const currentTime = new Date().getTime();// 判斷當前時間與上次執行時間差是否大于等待時間if (currentTime - lastTime > wait) {// 如果滿足條件,則執行原函數,并傳遞參數fn.apply(this, arguments);// 更新上一次執行的時間戳為當前時間lastTime = currentTime;}};
}// 使用
const throttledFn = throttleByTimestamp(function () {console.log("節流函數被執行");
}, 500); // 每隔500毫秒執行一次// 等待時間
const waitTime = async (time) => {return new Promise((resolve) => {setTimeout(() => {resolve();}, time);});
};const main = async() => {throttledFn();// 換成 < 500ms的則只會執行一次await waitTime(600);throttledFn();
}main()/*** 輸出:* 節流函數被執行* 節流函數被執行*/
定時器實現
原理
我們在事件觸發時設置一個定時器。
- 首次事件觸發時,我們設置一個定時器,在等待一段時間后執行函數。
- 當定時器觸發前,如果有新的事件觸發,我們會檢查是否存在定時器,如果存在則跳過。
- 當定時器觸發時,會清除當前定時器,確保下一次事件能重新觸發。
代碼
注: 測試例子跟上面的一樣,此處不在貼啦。
function throttleByTimer(fn, delay) {let timer;return function () {const context = this;const args = arguments;if (!timer) {timer = setTimeout(() => {fn.apply(context, args);clearTimeout(timer);timer = null;}, delay);}};
}
時間戳+定時器實現
我們回顧這兩個實現方式,大家有沒有發現這兩個實現方式的區別?
時間戳
: 因為是拿當前時間減上一次觸發的時間,所以一旦滿足該條件,事件會立即執行。定時器
: 由于fn.apply
的函數寫在了setTimeout
里,所以觸發了,也得等delay
后才能執行,于是就有了這種事件停止觸發后依然會再一次執行的效果。
如果我們要實現這樣的一個需求,完成一個事件觸發時立即執行,觸發完畢還能執行一次的節流函數該如何做呢?
原理
此處借鑒掘金網友《6個瑞士卷》的分析,個人覺得邏輯寫的很好。于是摘錄了下來。
- 需要在每個
delay
時間中一定會執行一次函數,因此在節流函數內部使用開始時間、當前時間與delay
來計算remaining
- 當
remaining <= 0
時表示該執行函數了,如果還沒到時間的話就設定在remaining
時間后再觸發。 - 當然在
remaining
這段時間中如果又一次發生事件,那么會取消當前的計時器,并重新計算一個remaining
來判斷當前狀態。
代碼
function throttle(fn, delay) {let timer;let lastTime = 0;return function () {let currentTime = Date.now();// 計算距離上次執行fn到現在過去了多少時間,與delay做比較let remaining = delay - (currentTime - lastTime);const context = this;const args = arguments;// 如果在remaining這段時間在發生,會取消當前的計時器clearTimeout(timer);// 當remaining <= 0時表示該執行函數了if (remaining <= 0) {fn.apply(context, args);lastTime = Date.now();} else {// 如果還沒到時間的話就設定在remaining時間后再觸發timer = setTimeout(fn, remaining);}};
}
借鑒文章
- JS簡單實現防抖和節流