JavaScript?防抖debounce與節(jié)流thorttle
前言:
防抖(Debounce) 和 節(jié)流(Throttle) 技術用于限制函數執(zhí)行的次數。通常,一個函數將被執(zhí)行多少次或何時執(zhí)行由開發(fā)人員決定。但在某些情況下,開發(fā)人員會將這種能力賦予用戶,由用戶決定執(zhí)行該功能的時間和次數。
例如,添加到click
、scroll
、resize
等事件上的函數,允許用戶決定何時執(zhí)行它們以及執(zhí)行多少次。有時,用戶可能會比所需更頻繁地執(zhí)行這些操作。這可能不利于網站的性能,特別是如果附加到這些事件的函數執(zhí)行一些繁重的計算。在這種情況下,用戶可以控制函數的執(zhí)行,開發(fā)人員必須設計一些技術來限制用戶可以執(zhí)行函數的次數。
舉個例子,假設我們?yōu)闈L動事件scroll
添加了一個函數,該函數中會執(zhí)行修改DOM元素的操作。我們知道,修改DOM元素大小開銷很大,會引起瀏覽器的回流(Reflow)和重排(Repaint),以及重新渲染整個或部分頁面。如果用戶頻繁滾動,導致該函數頻繁被調用,可能會影響網頁性能或導致頁面卡頓等。
此外,有些事件回調函數中包含ajax等異步操作的時候,多次觸發(fā)會導致返回的內容結果順序不一致,而導致得到的結果非最后一次觸發(fā)事件對應的結果
所以,為了優(yōu)化網頁的性能,控制函數被調用的頻率是很有必要的,防抖(Debounce) 和 節(jié)流(Throttle) 是通過控制函數被調用的頻率來優(yōu)化腳本性能的兩種方法
一、防抖(Debounce)
防抖:無論用戶觸發(fā)多少次事件,對應的回調函數只會在事件停止觸發(fā)指定事件后執(zhí)行。(即:回調函數在事件停止觸發(fā)指定時間后被調用)
例如,假設用戶在 100 毫秒內點擊了 5 次按鈕。防抖技術不會讓這些點擊中的任何一個執(zhí)行對應的回調函數。一旦用戶停止點擊,如果去抖時間為 100 毫秒,則回調函數將在 100 毫秒后執(zhí)行。因此,肉眼看來,防抖就像將多個事件組合成一個事件一樣。
1.1 防抖函數的實現
(1)版本1 —— 停止觸發(fā)事件n毫秒后執(zhí)行回調函數
觸發(fā)事件后函數不會立即執(zhí)行,而是在停止事件觸發(fā)后 n 毫秒后執(zhí)行,如果在 n 毫秒內又觸發(fā)了事件,則會重新計時
/** * @desc 函數防抖 * @param func 回調函數 * @param delay 延遲執(zhí)行毫秒數 */ function debounce(func, delay) { let timer; // 定時器 return function () { let context = this; // 記錄 this 值,防止在回調函數中丟失 let args = arguments; // 函數參數 //如果定時器存在,則清除定時器(如果沒有,也沒必要進行處理) timer ? clearTimeout(timer) : null; timer = setTimeout(() => { // 防止 this 值變?yōu)?window func.apply(context, args) }, delay); } }
(2)版本2
觸發(fā)事件后立即執(zhí)行回調函數,但是觸發(fā)后n毫秒內不會再執(zhí)行回調函數,如果 n 毫秒內觸發(fā)了事件,也會重新計時。
/** * @desc 函數防抖 * @param func 回調函數 * @param delay 延遲執(zhí)行毫秒數 */ function _debounce(func, delay) { let timer; // 定時器 return function () { let context = this; // 記錄 this 值,防止在回調函數中丟失 let args = arguments; // 函數參數 // 標識是否立即執(zhí)行 let isImmediately = !timer; //如果定時器存在,則清除定時器(如果沒有,也沒必要進行處理) timer ? clearTimeout(timer) : null; timer = setTimeout(() => { timer = null; }, delay); // isImmediately 為 true 則 執(zhí)行函數(即首次觸發(fā)事件) isImmediately ? func.apply(context, args) : null; } }
舉個例子來對比一下兩個版本的區(qū)別:
document.body.onclick= debounce(function () { console.log('hello') },1000)
如上代碼中,我們給body添加了一個點擊事件監(jiān)聽器。
- 如果是版本1的防抖函數,當我點擊body時,控制臺不會立即打印
hello
,要等 1000ms 后才會打印。在這 1000s 內如果還點擊了 body,那么就會重新計時。即最后一次點擊 body 過1000ms后控制臺才會打印hello
- 如果是版本2的防抖函數,當我首次點擊body時,控制臺會立馬打印
hello
,但是在此之后的 1000ms 內點擊 body ,控制臺不會有任何反應。在這 1000s 內如果還點擊了 body,那么就會重新計時。必須等計時結束后再點擊body,控制臺才會再次打印hello
。
1.2 防抖的實際應用
(1)搜索框建議項
通常,搜索框會提供下拉菜單,為用戶當前的輸入提供自動完成選項。但有時建議項是通過請求后端得到的??梢栽趯崿F提示文本時應用防抖,在等待用戶停止輸入一段時間后再顯示建議文本。因此,在每次擊鍵時,都會等待幾秒鐘,然后再給出建議。
(2)消除resize事件處理程序的抖動。
window 觸發(fā) resize 的時候,不斷的調整瀏覽器窗口大小會不斷觸發(fā)這個事件,用防抖讓其只觸發(fā)一次
(3)自動保存
例如掘金一類的網站,都會內嵌文本編輯器,在編輯過程中會自動保存文本,防止數據丟失。每次保存都會與后端進行數據交互,所以可以應用防抖,在用戶停止輸入后一段時間內再自動保存。
(4)手機號、郵箱等輸入驗證檢測
通常對于一些特殊格式的輸入項,我們通常會檢查格式。我們可以應用防抖在用戶停止輸入后一段時間再進行格式檢測,而不是輸入框中內容發(fā)生改變就檢測。
(5)在用戶停止輸入之前不要發(fā)出任何 Ajax 請求
二、節(jié)流(Throttle)
節(jié)流:無論用戶觸發(fā)事件多少次,附加的函數在給定的時間間隔內只會執(zhí)行一次。(即:回調函數在規(guī)定時間內最多執(zhí)行一次)
例如,當用戶單擊一個按鈕時,會執(zhí)行一個在控制臺上打印Hello, world
的函數?,F在,假設對這個函數應用 1000 毫秒的限制時,無論用戶點擊按鈕多少次,Hello, world
在 1000 毫秒內都只會打印一次。節(jié)流可確保函數定期執(zhí)行。
2.1 節(jié)流函數的實現
(1)版本1 —— 使用定時器
/** * @desc 函數節(jié)流 * @param func 回調函數 * @param limit 時間限制 */ const throttle = (func, limit) => { let inThrottle; // 是否處于節(jié)流限制時間內 return function() { const context = this; const args = arguments; // 跳出時間限制 if (!inThrottle) { func.apply(context, args); // 執(zhí)行回調 inThrottle = true; // 開啟定時器計時 setTimeout(() => inThrottle = false, limit); } } }
(2)版本2 —— 計算當前時間與上次執(zhí)行函數時間的間隔
/** * @desc 函數節(jié)流 * @param func 回調函數 * @param limit 時間限制 */ function throttle(func, limit) { //上次執(zhí)行時間 let previous = 0; return function() { //當前時間 let now = Date.now(); let context = this; let args = arguments; // 若當前時間-上次執(zhí)行時間大于時間限制 if (now - previous > limit) { func.apply(context, args); previous = now; } } }
很多現有的庫中已經實現了防抖函數和節(jié)流函數
2.2 節(jié)流的實際應用
(1)游戲中通過按下按鈕執(zhí)行的關鍵動作(例如:射擊、平A)
拿王者榮耀為例,通常都有攻速一說。如果攻速低,即使 n 毫秒內點擊平A按鈕多次,也只會執(zhí)行一次平A。其實這里就類似于節(jié)流的思想,可以通過設置節(jié)流的時間間隔限制,來改變攻速。
(2)滾動事件處理
如果滾動事件被觸發(fā)得太頻繁,可能會影響性能,因為它包含大量視頻和圖像。因此滾動事件必須使用節(jié)流
(3)限制mousemove/touchmove事件處理程序
小結
如何選擇防抖和節(jié)流:
關于防抖函數和節(jié)流函數的選擇,一篇博客中是這樣建議的:
A debounce is utilized when you only care about the final state. A throttle is best used when you want to handle all intermediate states but at a controlled rate.
即:如果只關心最終狀態(tài),建議使用防抖。如果是想要函數以可控的速率執(zhí)行,那么建議使用節(jié)流。
延時多久合理:
- 大多數屏幕的刷新頻率是每秒60Hz,瀏覽器的渲染頁面的標準幀率也為60FPS,瀏覽器每秒會重繪60次,而每幀之間的時間間隔是DOM視圖更新的最小間隔。
- 一個平滑而流暢的動畫,最佳的循環(huán)間隔即幀與幀的切換時間希望是 16.6ms(1s/60)內,也意味著17ms內的多次DOM改動會被合并為一次渲染。
- 當執(zhí)行回調函數時間大于16.6ms(系統屏幕限制的刷新頻率),UI將會出現丟幀(即UI這一刻不會被渲染),且丟幀越多,引起卡頓情況更嚴重。
到此這篇關于JavaScript 防抖debounce與節(jié)流thorttle的文章就介紹到這了,更多相關JavaScript 防抖與節(jié)流內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript Array Flatten 與遞歸使用介紹
用 JavaScript 將 [1,2,3,[4,5, [6,7]], [[[8]]]] 這樣一個 Array 變成 [1,2,3,4,5, 6,7,8] 呢?傳說中的 Array Flatten2011-10-10