在前端開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到一些需要頻繁觸發(fā)的事件,比如滾動(dòng)、窗口調(diào)整大小、鍵盤(pán)輸入等。這些事件如果處理不當(dāng),會(huì)導(dǎo)致性能問(wèn)題,甚至讓頁(yè)面變得卡頓。為了優(yōu)化這些高頻事件的處理,我們可以使用節(jié)流(Throttle)和防抖(Debounce)兩種技術(shù)。小編將詳細(xì)介紹節(jié)流功能的實(shí)現(xiàn)原理及其具體實(shí)現(xiàn)方法。
什么是節(jié)流(Throttle)?
節(jié)流(Throttle)是一種限制函數(shù)執(zhí)行頻率的技術(shù)。它確保在一定時(shí)間間隔內(nèi),函數(shù)只執(zhí)行一次,無(wú)論在這段時(shí)間內(nèi)該函數(shù)被觸發(fā)了多少次。節(jié)流常用于控制滾動(dòng)、窗口調(diào)整大小等事件的頻率,以避免過(guò)度消耗資源。
節(jié)流的基本原理
節(jié)流的基本原理是使用一個(gè)定時(shí)器來(lái)記錄上一次函數(shù)執(zhí)行的時(shí)間,并在每次觸發(fā)事件時(shí)檢查當(dāng)前時(shí)間與上一次執(zhí)行時(shí)間的差值。如果差值大于或等于設(shè)定的時(shí)間間隔,則執(zhí)行函數(shù),并更新上一次執(zhí)行時(shí)間;如果差值小于設(shè)定的時(shí)間間隔,則不執(zhí)行函數(shù)。
節(jié)流的具體實(shí)現(xiàn)
下面是一個(gè)簡(jiǎn)單的節(jié)流函數(shù)實(shí)現(xiàn):
function throttle(func, wait) { let lastTime = 0; // 上次執(zhí)行時(shí)間 return function(...args) { const now = Date.now(); // 當(dāng)前時(shí)間 if (now - lastTime >= wait) { // 當(dāng)前時(shí)間與上次執(zhí)行時(shí)間的差值是否大于或等于設(shè)定的時(shí)間間隔 lastTime = now; // 更新上次執(zhí)行時(shí)間 func.apply(this, args); // 執(zhí)行函數(shù) } }; }
使用示例
假設(shè)我們有一個(gè)滾動(dòng)事件監(jiān)聽(tīng)器,每次滾動(dòng)時(shí)都會(huì)執(zhí)行一個(gè)函數(shù)。我們可以使用節(jié)流來(lái)限制這個(gè)函數(shù)的執(zhí)行頻率:
function onScroll() { console.log('Scroll event triggered'); // 其他滾動(dòng)處理邏輯 } // 綁定滾動(dòng)事件監(jiān)聽(tīng)器,并使用節(jié)流函數(shù)限制執(zhí)行頻率 window.addEventListener('scroll', throttle(onScroll, 200));
在這個(gè)例子中,無(wú)論滾動(dòng)事件被觸發(fā)多少次,onScroll 函數(shù)每 200 毫秒最多只會(huì)被執(zhí)行一次。
改進(jìn)版節(jié)流函數(shù)
上面的節(jié)流函數(shù)有一個(gè)問(wèn)題:如果在設(shè)定的時(shí)間間隔內(nèi)最后一次觸發(fā)事件后,函數(shù)沒(méi)有被執(zhí)行(因?yàn)闀r(shí)間間隔未到),那么最后一次觸發(fā)事件的處理邏輯就會(huì)被忽略。為了解決這個(gè)問(wèn)題,我們可以引入一個(gè)定時(shí)器,在最后一次觸發(fā)事件時(shí)確保函數(shù)能夠執(zhí)行。
function throttle(func, wait) { let lastTime = 0; // 上次執(zhí)行時(shí)間 let timeoutId = null; // 定時(shí)器ID return function(...args) { const now = Date.now(); // 當(dāng)前時(shí)間 if (!timeoutId) { // 如果沒(méi)有定時(shí)器 if (now - lastTime >= wait) { // 當(dāng)前時(shí)間與上次執(zhí)行時(shí)間的差值是否大于或等于設(shè)定的時(shí)間間隔 lastTime = now; // 更新上次執(zhí)行時(shí)間 func.apply(this, args); // 執(zhí)行函數(shù) } else { // 如果時(shí)間間隔未到,設(shè)置定時(shí)器在設(shè)定的時(shí)間間隔后執(zhí)行函數(shù) timeoutId = setTimeout(() => { lastTime = Date.now(); // 更新上次執(zhí)行時(shí)間 func.apply(this, args); // 執(zhí)行函數(shù) timeoutId = null; // 清除定時(shí)器ID }, wait - (now - lastTime)); } } };