JavaScript的防抖和節(jié)流一起來了解下
1. 前言
首先來舉個(gè)例子。百度首頁的百度輸入框,用戶輸入的時(shí)候,每次輸入的信息,我們都能看到百度服務(wù)器返回給我們的聯(lián)想關(guān)鍵字。我們每改動(dòng)一個(gè)字,它就換一次聯(lián)想詞,這是我們?nèi)庋勰芸吹降乃俣?,?shí)際上如果不加以處理,可能已經(jīng)上服務(wù)器發(fā)起了好幾十次的同一個(gè)關(guān)鍵字聯(lián)想請(qǐng)求了,具體速度依賴于不同的pc等機(jī)器上的運(yùn)行速度不同。那么,剛剛也談到,對(duì)于同一個(gè)關(guān)鍵字,請(qǐng)求這么多次,也許想給用戶呈現(xiàn)的就一次,剩下的請(qǐng)求都是浪費(fèi)的,并且如果成千上萬甚至上億的用戶同時(shí)請(qǐng)求,對(duì)服務(wù)器的負(fù)擔(dān)是巨大的。
防抖節(jié)流解決的問題:
在連續(xù)觸發(fā)的事件中,事件處理函數(shù)的頻繁調(diào)用會(huì)加重瀏覽器或服務(wù)器的性能負(fù)擔(dān)導(dǎo)致用戶體驗(yàn)糟糕,有哪些連續(xù)觸發(fā)的事件呢 ?
比如,瀏覽器滾動(dòng)條的滾動(dòng)事件、瀏覽器窗口調(diào)節(jié)的resize事件、輸入框內(nèi)容校驗(yàn)以及在移動(dòng)端的touchmove事件等。
所以,我們將采用防抖函數(shù)(debounce )和節(jié)流函數(shù)(throttle)來限制事件處理函數(shù)的調(diào)用頻率。
總的來說:防抖函數(shù)(debounce )和節(jié)流函數(shù)(throttle)是在時(shí)間軸上控制函數(shù)的執(zhí)行次數(shù)。
2. 函數(shù)防抖(debounce)
延遲防抖
延遲防抖(debounce): 在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí)。
生活中的實(shí)例: 如果有人進(jìn)電梯(觸發(fā)事件),那電梯將在10秒鐘后出發(fā)(執(zhí)行事件監(jiān)聽器),這時(shí)如果又有人進(jìn)電梯了(在10秒內(nèi)再次觸發(fā)該事件),我們又得等10秒再出發(fā)(重新計(jì)時(shí))。
當(dāng)持續(xù)觸發(fā)事件時(shí),一定時(shí)間段內(nèi)沒有再觸發(fā)事件,事件處理函數(shù)才會(huì)執(zhí)行一次。
如果設(shè)定的時(shí)間到來之前,又一次觸發(fā)了事件,就重新開始延時(shí)。
如下圖,持續(xù)觸發(fā)click事件時(shí),并不執(zhí)行handle函數(shù),當(dāng)1000毫秒內(nèi)沒有觸發(fā)click事件時(shí),才會(huì)延時(shí)觸發(fā)click事件。
前緣防抖
執(zhí)行動(dòng)作在前,然后設(shè)定周期,周期內(nèi)有事件被觸發(fā),不執(zhí)行動(dòng)作,且周期重新設(shè)定。
為什么要這樣呢?
試想第一種延遲debounce,我們本來想對(duì)用戶輸入的關(guān)鍵字,發(fā)起請(qǐng)求聯(lián)想的頻率降低,但是如果用戶在我們?cè)O(shè)定的時(shí)間中,一直輸入,導(dǎo)致的就是,用戶一直看不到關(guān)鍵字,我們倒不如第一次輸入的時(shí)候就發(fā)起一個(gè)請(qǐng)求,服務(wù)器返回結(jié)果,呈現(xiàn)給用戶,然后后續(xù)用戶的鍵入結(jié)束在繼續(xù)請(qǐng)求)。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>防抖</title> </head> <body> <button id="debounce1">點(diǎn)我防抖吶!</button> <script> function handle() { console.log("防抖成功!"); } window.onload = function() { // 1. 獲取按鈕,并綁定事件 var myDebounce = document.getElementById('debounce1'); myDebounce.addEventListener('click',debounce(handle,1000,true)); } // 防抖函數(shù) function debounce(fn,wait, immediate ){ //2. 設(shè)置時(shí)間戳,使用setTimeout讓返回函數(shù)延遲執(zhí)行 let timer, result; return function(...args){ // 3. timer存在,將定時(shí)器中的函數(shù)清除 if(timer) clearTimeout(timer); // 4.1 立即執(zhí)行返回函數(shù) if(immediate){ if(!timer){ result = fn.apply(this,args); } timer = setTimeout(() => { timer = null; },wait); }else{ // 4.2 非立即執(zhí)行返回函數(shù) timer = setTimeout(() => { fn.apply(this,args); },wait); } } // 5. 立即執(zhí)行時(shí)返回函數(shù)的返回值 return result; } </script> </body> </html>
實(shí)現(xiàn)效果:
原理解析:
- 防抖函數(shù)作用,對(duì)傳入的函數(shù)進(jìn)行延時(shí)包裝后返回
- setTimeout在前一次未執(zhí)行完前,第二次次觸發(fā)將會(huì)覆蓋掉前面的定時(shí)器,執(zhí)行第二次的功能
- 前一次由于異步加延時(shí)還未執(zhí)行完,使用clearTimeout清除前面定時(shí)器,取消上次的fn功能
- 為保持fn內(nèi)部this的指向,使用apply改變this指向
- fn傳入為函數(shù),不是函數(shù)的調(diào)用
防抖函數(shù)實(shí)現(xiàn)總結(jié)
function debounce(fn,wait, immediate ){ //2. 設(shè)置時(shí)間戳,使用setTimeout讓返回函數(shù)延遲執(zhí)行 let timer, result; return function(...args){ // 3. timer存在,將定時(shí)器中的函數(shù)清除 if(timer) clearTimeout(timer); // 4.1 立即執(zhí)行返回函數(shù) if(immediate){ if(!timer){ result = fn.apply(this,args); } timer = setTimeout(() => { timer = null; },wait); }else{ // 4.2 非立即執(zhí)行返回函數(shù) timer = setTimeout(() => { fn.apply(this,args); },wait); } } // 5. 立即執(zhí)行時(shí)返回函數(shù)的返回值 return result; }
3. 函數(shù)節(jié)流(throttling)
throttling,節(jié)流的策略是,固定周期內(nèi),只執(zhí)行一次動(dòng)作,若有新事件觸發(fā),不執(zhí)行。周期結(jié)束后,又有事件觸發(fā),開始新的周期。 節(jié)流策略也分前緣和延遲兩種。
與debounce類似,延遲是指 周期結(jié)束后執(zhí)行動(dòng)作,前緣是指執(zhí)行動(dòng)作后再開始周期。
- 節(jié)流會(huì)稀釋函數(shù)的執(zhí)行頻率
- 在持續(xù)觸發(fā)事件的過程中,函數(shù)會(huì)立即執(zhí)行,并且每n秒執(zhí)行一次
生活中的實(shí)例: 我們知道目前的一種說法是當(dāng) 1 秒內(nèi)連續(xù)播放 24 張以上的圖片時(shí),在人眼的視覺中就會(huì)形成一個(gè)連貫的動(dòng)畫,所以在電影的播放(以前是,現(xiàn)在不知道)中基本是以每秒 24 張的速度播放的,為什么不 100 張或更多是因?yàn)?24 張就可以滿足人類視覺需求的時(shí)候,100 張就會(huì)顯得很浪費(fèi)資源
延遲節(jié)流
前緣節(jié)流
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>節(jié)流</title> </head> <body> <button id="debounce1">點(diǎn)我節(jié)流吶!</button> <script> function handle() { console.log("節(jié)流成功!"); } window.onload = function() { var myDebounce = document.getElementById('debounce1'); myDebounce.addEventListener('click',throttling(handle,1000,false)); } // 節(jié)流函數(shù) function throttling(fn,wait,immediate){ let timer; return function(...args) { if(!timer){ if(immediate){ fn.apply(this,args); } timer = setTimeout(() => { if(!immediate) { fn.apply(this,args); } timer = null; },wait); } } } </script> </body> </html>
實(shí)現(xiàn)效果:
節(jié)流函數(shù)實(shí)現(xiàn)總結(jié)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>節(jié)流</title> </head> <body> <button id="debounce1">點(diǎn)我節(jié)流吶!</button> <script> function handle() { console.log("節(jié)流成功!"); } window.onload = function() { var myDebounce = document.getElementById('debounce1'); myDebounce.addEventListener('click',throttling(handle,1000,false)); } // 節(jié)流函數(shù) function throttling(fn,wait,immediate){ let timer; return function(...args) { if(!timer){ if(immediate){ fn.apply(this,args); } timer = setTimeout(() => { if(!immediate) { fn.apply(this,args); } timer = null; },wait); } } } </script> </body> </html>
4. 兩者區(qū)別
函數(shù)節(jié)流不管事件觸發(fā)有多頻繁,都會(huì)保證在規(guī)定時(shí)間內(nèi)一定會(huì)執(zhí)行一次真正的事件處理函數(shù)。
函數(shù)防抖只是在最后一次事件后才觸發(fā)一次函數(shù)。
比如在頁面的無限加載場(chǎng)景下,我們需要用戶在滾動(dòng)頁面時(shí),每隔一段時(shí)間發(fā)一次 Ajax 請(qǐng)求,而不是在用戶停下滾動(dòng)頁面操作時(shí)才去請(qǐng)求數(shù)據(jù)。這樣的場(chǎng)景,就適合用節(jié)流技術(shù)來實(shí)現(xiàn)。
5. 應(yīng)用場(chǎng)景
對(duì)于函數(shù)防抖,有以下幾種應(yīng)用場(chǎng)景:
- 給按鈕加函數(shù)防抖防止表單多次提交。
- 對(duì)于輸入框連續(xù)輸入進(jìn)行AJAX驗(yàn)證時(shí),用函數(shù)防抖能有效減少請(qǐng)求次數(shù)。
- 判斷scroll是否滑到底部,滾動(dòng)事件+函數(shù)防抖
總的來說,適合多次事件一次響應(yīng)的情況
對(duì)于函數(shù)節(jié)流,有如下幾個(gè)場(chǎng)景:
- 游戲中的刷新率
- DOM元素拖拽
- Canvas畫筆功能
總的來說,適合大量事件按時(shí)間做平均分配觸發(fā)。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- 利用JavaScript實(shí)現(xiàn)防抖節(jié)流函數(shù)的示例代碼
- JavaScript函數(shù)防抖與函數(shù)節(jié)流的定義及使用詳解
- JS中節(jié)流和防抖函數(shù)的實(shí)現(xiàn)及區(qū)別示例
- JavaScript防抖動(dòng)與節(jié)流處理
- JavaScript中防抖和節(jié)流的區(qū)別及適用場(chǎng)景
- 詳細(xì)聊一聊js防抖節(jié)流到底是什么
- JavaScript防抖與節(jié)流的實(shí)現(xiàn)與注意事項(xiàng)
- JavaScript中函數(shù)的防抖與節(jié)流詳解
- JavaScript防抖與節(jié)流超詳細(xì)全面講解
相關(guān)文章
JavaScript 函數(shù)節(jié)流詳解及方法總結(jié)
這篇文章主要介紹了JavaScript 函數(shù)節(jié)流詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02OpenLayers實(shí)現(xiàn)點(diǎn)要素圖層的聚合顯示的方法
在很多情況下,點(diǎn)要素圖層中的要素?cái)?shù)量可能會(huì)成百上千,如果一個(gè)點(diǎn)要素圖層中的點(diǎn)數(shù)量很多,我們就會(huì)采取圖層聚合的方式對(duì)其進(jìn)行處理,本文就來介紹一下,感興趣的可以了解一下2021-09-09JS計(jì)算兩個(gè)數(shù)組的交集、差集、并集、補(bǔ)集(多種實(shí)現(xiàn)方式)
本文通過多種實(shí)現(xiàn)方式給大家介紹了JS計(jì)算兩個(gè)數(shù)組的交集、差集、并集、補(bǔ)集 的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05JS+CSS實(shí)現(xiàn)的豎向簡潔折疊菜單效果代碼
這篇文章主要介紹了JS+CSS實(shí)現(xiàn)的豎向簡潔折疊菜單效果代碼,涉及JavaScript鏈?zhǔn)讲僮髋c元素遍歷等相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10ECMA5數(shù)組的新增方法有哪些及forEach()模仿實(shí)現(xiàn)
這篇文章主要介紹了ECMA5數(shù)組的新增方法有哪些及forEach()模仿實(shí)現(xiàn),需要的朋友可以參考下2015-11-11