常規(guī)防抖節(jié)流函數(shù)
方法
function debounce(fn, delay = 500) {
let timeout = null;
return function() {
if (timeout) {
clearTimeout(timeout)
}
timeout= setTimeout(() => {
fn.apply(this, arguments)
timeout = null
}, delay)
}
}
function throttle(fn, delay) {
let timeout = null
return function() {
if (timeout) {
return
}
timeout = setTimeout(() => {
fn.apply(this, arguments)
timeout = null
}, delay)
}
}
使用
onclick:debounce(實(shí)際操作函數(shù), 時(shí)間)
onclick:throttle(實(shí)際操作函數(shù), 時(shí)間)
實(shí)際使用出現(xiàn)的問題
- 使用閉包來區(qū)分不同按鈕的操作,也正因?yàn)槿绱藷o法進(jìn)行多按鈕處理
- 對(duì)于持續(xù)性操作(滾動(dòng),縮放)應(yīng)當(dāng)使用節(jié)流操作,但無法保證最后一次操作執(zhí)行
優(yōu)化后
方法
type Config = {
fn: () => void; // 待執(zhí)行的函數(shù)
key: string; // 唯一標(biāo)識(shí)符,用于區(qū)分不同的操作
time: number; // 延遲時(shí)間,單位毫秒
}
type ConfigParams = Config['fn'] | {
fn: Config['fn'],
key?: Config['key'],
time?: Config['time']
}
/** 延遲集合,記錄每個(gè) key 對(duì)應(yīng)的延遲信息 */
const delayMap: Record<string, { fn: Config['fn'], timeout: number }> = {}
/** 獲取配置項(xiàng),合并默認(rèn)配置和傳入的配置 */
function getConfig(configParams: ConfigParams, key: Config['key'], time: Config['time']): Config {
let config: Config = { fn: () => { }, key, time }
if (typeof configParams === 'function') {
// 如果傳入的是函數(shù)類型,則賦值給 fn
config.fn = configParams
} else if (typeof config === 'object') {
// 如果傳入的是對(duì)象類型,則合并對(duì)象配置
config = { ...config, ...configParams }
}
return config
}
/** 記錄延遲,并在設(shè)定時(shí)間后執(zhí)行內(nèi)部方法 */
function delayTimeOut(key: Config['key'], time: Config['time']) {
if (!time) return; // 如果時(shí)間為 0 或者無效時(shí)間,跳過
// 清理已有的延遲操作
const clearFn = () => {
clearTimeout(delayMap[key].timeout)
delete delayMap[key];
}
// 記錄當(dāng)前的延遲操作
delayMap[key] = {
fn: clearFn, // 默認(rèn)的清理函數(shù)
timeout: setTimeout(() => {
// 延遲時(shí)間到達(dá)時(shí),執(zhí)行傳入的待執(zhí)行函數(shù)
if (delayMap[key]) delayMap[key].fn();
// 若延遲未被清理,說明更換了內(nèi)部方法(執(zhí)行了有效操作)
if (delayMap[key]) {
clearFn()// 清理當(dāng)前延遲
delayTimeOut(key, time);// 重新記錄延遲操作
}
}, time)
}
}
/** 防抖函數(shù):延遲執(zhí)行,若在延遲時(shí)間內(nèi)重復(fù)觸發(fā),清除之前的延遲操作 */
export function debounce(configParams: ConfigParams) {
if (!configParams) return // 如果沒有傳入配置,直接返回
const config = getConfig(configParams, 'debounce', 750) // 獲取完整配置
// 如果已經(jīng)有相同 key 的延遲操作在進(jìn)行,清除之前的延遲
if (delayMap[config.key]) {
clearTimeout(delayMap[config.key].timeout)
delete delayMap[config.key];
}
// 開啟新的延遲操作
delayTimeOut(config.key, config.time);
delayMap[config.key].fn = config.fn// 記錄新的待執(zhí)行函數(shù)
}
/** 節(jié)流函數(shù):規(guī)定時(shí)間內(nèi)只執(zhí)行一次,防止過于頻繁的調(diào)用 */
export function throttle(configParams: ConfigParams) {
if (!configParams) return // 如果沒有傳入配置,直接返回
const config = getConfig(configParams, 'throttle', 750) // 獲取完整配置
// 如果當(dāng)前沒有延遲操作,則立即執(zhí)行待執(zhí)行函數(shù)
if (!delayMap[config.key]) {
config.fn()
delayTimeOut(config.key, config.time);
}
}
/** 延遲函數(shù):相比節(jié)流函數(shù)會(huì)在等待時(shí)間中記錄并在結(jié)束后執(zhí)行最后一次操作保證數(shù)據(jù)準(zhǔn)確 */
export function delay(configParams: ConfigParams) {
if (!configParams) return // 如果沒有傳入配置,直接返回
const config = getConfig(configParams, 'delay', 100) // 獲取完整配置
// 如果尚未開啟延遲操作,執(zhí)行一次后開啟延遲;如果已經(jīng)開啟延遲,則更換內(nèi)部方法
if (!delayMap[config.key]) {
config.fn()
delayTimeOut(config.key, config.time);
} else {
delayMap[config.key].fn = config.fn;
}
}
使用
onclick(){
debounce(實(shí)際操作函數(shù))
// 或
debounce({fn:實(shí)際操作函數(shù),time:時(shí)間,key:標(biāo)識(shí)})
}
onclick(){
throttle(實(shí)際操作函數(shù))
// 或
throttle({fn:實(shí)際操作函數(shù),time:時(shí)間,key:標(biāo)識(shí)})
}
onclick(){
delay(實(shí)際操作函數(shù))
// 或
delay({fn:實(shí)際操作函數(shù),time:時(shí)間,key:標(biāo)識(shí)})
}
說明
delay是節(jié)流方法的延伸,添加了最后執(zhí)行一次的邏輯,處理高頻且保證結(jié)果準(zhǔn)確
- 使用顯式調(diào)用使用key關(guān)鍵字作為區(qū)分,可有效解決多按鈕功能重復(fù)等帶來的節(jié)流難題