前端效能優化(2) - Throttle
JavaScript
#Notes #JavaScript #Frontend Throttle 也是一種常見的效能優化方式,與 Debounce 類似,但不同的地方在 Throttle 能確保一個函式在一段時間內只會觸發一次,無論該函式被呼叫幾次。
節流 ( Throttle )
舉一個常見的例子
當在滑 Twitter 時,一個長頁面下需要監聽滾動同時又需要新增內容,因為要判斷是否已經滾動到接近底部再去新增內容,所以會在滾動的過程不斷去計算範圍
但使用 Throttle 可以有效的控制事件觸發的頻率,減少過度頻繁的計算與不必要的 api request
其他常見的用途還有:
- 即時搜尋
- 即時數據更新
- 調整視窗大小時重新布局
- 快速點擊事件
- 拖拽元素
Throttle 與 Debounce 比較
-
Throttle:執行過程中固定時間間隔內執行一次。適合需要持續反饋但又不希望過於頻繁的場景。
-
Debounce:停下來等待一段時間後執行,如果在等待期間再次調用則重新計時。適合等待用戶操作完成後再執行的場景。
實作
實作 Throttle 的方向跟 Debounce 類似:
- closure
- setTimeout
- 接收兩個參數:要執行 Throttle 的 callback function 和 delay 時間
function throttle(func, delay = 1000) {
let timer = null;
// ...args 用於接收所有參數
return (...args) => {
// 如果 timer 還在秒數內則直接 return
if (timer) return;
timer = setTimeout(() => {
timer = null;
}, delay);
// 確保 func 能立即執行
func.apply(this, args);
};
}
// callback function
function handleScroll() {
let clientHeight - document.documentElement.clientHeight;
let scrollTop - document.documentElement.scrollTop;
let scrollHeight - document.documentElement.scrollHeight;
// 判斷到達底部 90% 位置新增內容
if ((scrollTop + clientHeight) / scrollHeight >= 0.9) {
for(let i=0; i<=10; i++) {
console.log("一段新內容");
}
}
}
// 使用方法
const throttledHandleScroll = throttle(handleScroll, 1500);
window.addEventListener('scroll', throttledHandleScroll);
Throttle 的目的是限制某個函數在一段時間內只能執行一次
一開始將 timer 設為 null 表示沒有在計時,如果 timer 不是 null 代表正在計時,則直接 return。
如果 timer 為 null 則執行 setTimeout()
開始計時,當 setTimeout()
計時結束會重製 timer
,所以 func()
在每個 delay 時間內只會執行一次,即使 timer 不斷觸發。
封裝成 React Hook
通常 Throttle 有兩種情況
- 立即執行:在第一次呼叫的時候執行,然後透過 timer 在 delay 時間內阻止函式再次執行。
- 延遲執行:在一段時間內只執行一次,通常是在 delay 時間結束後執行最後一次函式,在這段時間內的多次事件觸發將被忽略。
可以根據自己需求調整 func()
要寫在哪
如果想改成立即執行,可以寫在 setTimeout()
外,但要注意 hook 可能會被頻繁使用,因此建議寫在 setTimeout()
內
建立 /hooks
資料夾,創建 useThrottle.js
檔案
import { useEffect, useRef } from 'react';
function useThrottle(func, delay) {
const timerRef = useRef(null);
const throttledFunc = (...args) => {
if (timerRef.current) return;
timerRef.current = setTimeout(() => {
timerRef.current = null;
// 一段時間執行一次
func(...args);
}, delay);
};
return throttledFunc;
}
export default useThrottle;
匯入到 Component 中就可以使用了
import React, { useEffect } from 'react';
import useThrottle from './useThrottle'; // useThrottle hook
function ScrollComponent() {
const handleScroll = () => {
const clientHeight = document.documentElement.clientHeight;
const scrollTop = document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
if ((scrollTop + clientHeight) / scrollHeight >= 0.9) {
for (let i = 0; i <= 10; i++) {
console.log("一段新內容");
}
}
};
// 使用 useThrottle 來限制 handleScroll 的執行頻率
const throttledHandleScroll = useThrottle(handleScroll, 1500);
useEffect(() => {
window.addEventListener('scroll', throttledHandleScroll);
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
};
}, []);
return <div>ScrollComponent</div>;
}
export default ScrollComponent;