JavaScript中防抖和節(jié)流的實戰(zhàn)應用記錄
前言
你可能會遇到這種的情況,一個站點使用自動填充的文本框,內(nèi)容的拖拽,效果的滾動。那么,你遇到防抖和截流的概率還是很高的。為了使得這些操作,比如自動填充能夠順暢工作,你需要引入防抖和截流功能。
- 防抖 -> Debounce
- 節(jié)流 -> Throttle
為什么我們需要防抖/節(jié)流
開篇已經(jīng)簡單提了,debounce/throttle
能讓你的站點表現(xiàn)更優(yōu)異。它們工作原理是通過減少動作發(fā)起的次數(shù)。我們簡單舉個例子,自動填充文本框觸發(fā)接口請求,如下:
input.addEventListener("input", e => { fetch(`/api/getOptions?query=${e.target.value}`) .then(res => res.json()) .then(data => setOptions(data)) })
??上面的事件監(jiān)測器,監(jiān)聽到輸入文本框發(fā)生更改,就基于文本框的內(nèi)容觸發(fā)一個查詢接口。這看起來還不錯,但是用戶輸入 Samantha 文字會發(fā)生什么?
當用戶輸入 S,事件監(jiān)測器觸發(fā)請求,并帶上選項 S。當此請求正在調(diào)用的時候,Sa 輸入內(nèi)容會再次被監(jiān)聽,我們將重新以 Sa 為選項內(nèi)容發(fā)起新的請求。以此類推,這種請求會持續(xù)到我們輸完 Samantha 的內(nèi)容。
這會在短時間內(nèi)發(fā)起 8 次請求,但是我們只關心最后一次請求。這意味著前 7 的接口請求都是不必要的,純屬浪費時間和金錢。
為了避免不必要的請求發(fā)生,我們就需要防抖和截流。
防抖
我們先來談下防抖,因為它是解決自動文本框類問題的理想解決方案。防抖的原理是延遲一段時間吊起我們的函數(shù)。如果在這個時間段沒有發(fā)生什么,函數(shù)正常進行,但是有內(nèi)容發(fā)生變更后的一段時間觸發(fā)函數(shù)。這就意味著,防抖函數(shù)只會在特定的時間之后被觸發(fā)。
在我們的例子中,我們假設延遲 1 秒觸發(fā)。也就是當用戶停止輸入內(nèi)容后 1 秒,接口強求被吊起。如果我們在 1 秒內(nèi)輸完 Samantha 內(nèi)容,請求查詢內(nèi)容就是 Samantha。下面我們看看怎么應用防抖。
function debounce(cb, delay = 250) { let timeout return (...args) => { clearTimeout(timeout) timeout = setTimeout(() => { cb(...args) }, delay) } }
上面的防抖函數(shù)帶有 cb 回調(diào)函數(shù)參數(shù)和一個延時的參數(shù)。我們在 debound 函數(shù)后返回回調(diào)函數(shù),這種包裝的方式,保證過了 delay 秒之后,回調(diào)函數(shù)才會被調(diào)用。最后,我們在每次調(diào)用 debounce 函數(shù)時清楚現(xiàn)有的定時器,以確保我們在延遲完成之前調(diào)用 debouce 函數(shù),并重新計時。
這意味著如果用戶在 1 秒內(nèi),每隔 300毫秒觸發(fā)一次輸入,上面 debouce 函數(shù)如下方式工作。
// Type S - Start timer // Type a - Restart timer // Type m - Restart timer // Type a - Restart timer // Type n - Restart timer // Wait 1 second // Call debounced function with Saman // Type t - Start timer // No more typing // Call debounced function with Samant
不知你注意到?jīng)]有,即使我們已經(jīng)輸入了 Saman 文案動作超過了一秒中,回調(diào)函數(shù)也不會調(diào)起,知道再過 1 秒鐘才被調(diào)用。現(xiàn)在,看我們怎么引用防抖函數(shù)。
const updateOptions = debounce(query => { fetch(`/api/getOptions?query=${query}`) .then(res => res.json()) .then(data => setOptions(data)) }, 1000) input.addEventListener("input", e => { updateOptions(e.target.value) )}
我們把請求函數(shù)放到回調(diào)函數(shù)中。
防抖函數(shù)在自動填充的情形非常好用,你也可以使用在其他地方,你想將多個觸發(fā)請求變成一個觸發(fā),以緩解服務器的壓力。
節(jié)流
像防抖一樣,節(jié)流也是限制請求的多次發(fā)送;但是,不同的是,防抖是每隔指定的時間發(fā)起請求。舉個例子,如果你在 throttle 函數(shù)中設置延遲時間是 1 秒,函數(shù)被調(diào)用執(zhí)行,用戶輸入每隔 1秒發(fā)起請求。看下下面的應用,你就明白了。
function throttle(cb, delay = 250) { let shouldWait = false return (...args) => { if (shouldWait) return cb(...args) shouldWait = true setTimeout(() => { shouldWait = false }, delay) } }
debounce 和 throttle 函數(shù)都有一樣的參數(shù),但是它們主要的不同是,throttle 中的回調(diào)函數(shù)在函數(shù)執(zhí)行后立馬被調(diào)用,并且回調(diào)函數(shù)不在定時器函數(shù)內(nèi)?;卣{(diào)函數(shù)要做的唯一事情就是將 shouldWait 標識設置為 false。當我們第一次調(diào)用 throttle 函數(shù),會將 shouldWait 標識設置為 true。這延時的時間內(nèi)再次調(diào)用 throttle 函數(shù),那就什么都不做。當時間超出了延時的時間,shouldWait 標識才會設置為 false。
假設我們每隔 300 毫秒輸入一個字符,然后我們的延時是 1 秒。那么 throttle 函數(shù)會像下面這樣工作:
// Type S - Call throttled function with S // Type a - Do nothing: 700ms left to wait // Type m - Do nothing: 400ms left to wait // Type a - Do nothing: 100ms left to wait // Delay is over - Nothing happens // Type n - Call throttled function with Saman // No more typing // Delay is over - Nothing happens
如果你留意看,你會發(fā)現(xiàn)第 1200 毫秒的時候,第二次 throttle 函數(shù)才會被觸發(fā)。已經(jīng)延遲了我們預設時間 200 毫秒。對于節(jié)流的需求來說,目前的 throttle 函數(shù)已經(jīng)滿足了需求。但是我們做些優(yōu)化,一旦 throttle 函數(shù)中的延時結束,我們就調(diào)用函數(shù)的前一個迭代。我們像下面這樣子應用。
function throttle(cb, delay = 1000) { let shouldWait = false let waitingArgs const timeoutFunc = () => { if (waitingArgs == null) { shouldWait = false } else { cb(...waitingArgs) waitingArgs = null setTimeout(timeoutFunc, delay) } } return (...args) => { if (shouldWait) { waitingArgs = args return } cb(...args) shouldWait = true setTimeout(timeoutFunc, delay) } }
上面的代碼有點嚇人,但是原理都一樣。不同的是,在 throttle 函數(shù)延時時,后者存儲了前一個 args 參數(shù)值作為變量 waitingArgs。當延遲完成后,我們會檢查 waitingArgs 是否有內(nèi)容。如果沒有內(nèi)容,我們會將 shouldWait 設置為 false,然后進入下一次觸發(fā)。如果 waitingArgs 有內(nèi)容,這就意味著延時到了之后,我們將會帶上 waitingArgs 參數(shù)觸發(fā)我們的回調(diào)函數(shù),然后重置我們的定時器。
這個版本的 throttle 函數(shù)也是延時時間為 1 秒,每隔 300 毫秒輸入值,效果如下:
// Type S - Call throttled function with S // Type a - Save Sa to waiting args: 700ms left to wait // Type m - Save Sam to waiting args: 400ms left to wait // Type a - Save Sama to waiting args: 100ms left to wait // Delay is over - Call throttled function with Sama // Type n - Save Saman to waiting args: 700ms left to wait // No more typing // Delay is over - Call throttled function with Saman
正如你所看到的,每次我們觸發(fā) throttle 函數(shù)時,如果延時時間結束,我們要么調(diào)用回調(diào)函數(shù),要么保存要在延時結束時使用的參數(shù)。如果這個參數(shù)有值的話,當延時結束時,我們將使用它。這就保證了 throttle 函數(shù)在延時結束時獲取到最新的參數(shù)值。
我們看下怎么應用到我們的例子中。
const updateOptions = throttle(query => { fetch(`/api/getOptions?query=${query}`) .then(res => res.json()) .then(data => setOptions(data)) }, 500) input.addEventListener("input", e => { updateOptions(e.target.value) )}
你會發(fā)現(xiàn),我們的應用跟 debounce 函數(shù)很相似,除了將 debounce 名改為 throttle。
當然,自動填充文本內(nèi)容例子,對 throttle 函數(shù)并不適用,但是,如果你處理類如更改元素大小,元素拖拉拽,或者其他多次發(fā)生的事件,那么 throttle 函數(shù)是理想的選擇。因為 throttle 每次延時結束時,你都會獲得有關事件的更新信息,而 debounce 需要等待輸入后延時后才能觸發(fā)。總的來說,當你想定期將多個事件組合成一個事件時, throttle 是理想的選擇。
本文為譯文,采用意譯
嗯~
我們來總結下,讀完了上面的內(nèi)容,可以簡單這么理解:
- 防抖:你可以無限限次觸發(fā),但是在指定的 Delay 時間內(nèi)監(jiān)聽到你沒有新的觸發(fā)事件了,就該我主角上場了。
- 節(jié)流:不管你觸發(fā)多少次,在指定的 Delay 時間到了以后,我必須上場一次
總結
到此這篇關于JavaScript中防抖和節(jié)流的文章就介紹到這了,更多相關JavaScript防抖和節(jié)流應用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- JavaScript函數(shù)防抖動debounce
- 通過實例講解JS如何防抖動
- JavaScript運動框架 解決防抖動問題、懸浮對聯(lián)(二)
- JavaScript中防抖和節(jié)流的區(qū)別及適用場景
- JavaScript深入理解節(jié)流與防抖
- JavaScript防抖與節(jié)流的實現(xiàn)與注意事項
- JavaScript的防抖和節(jié)流一起來了解下
- JavaScript中函數(shù)的防抖與節(jié)流詳解
- javascript的防抖和節(jié)流你了解嗎
- 淺談JavaScript節(jié)流與防抖
- 關于JavaScript防抖與節(jié)流的區(qū)別與實現(xiàn)
- JavaScript防抖與節(jié)流詳解
- JavaScript 防抖和節(jié)流詳解
- JavaScript防抖動與節(jié)流處理
相關文章
使用layui日期控件laydate對開始和結束時間進行聯(lián)動控制的方法
今天小編就為大家分享一篇使用layui日期控件laydate對開始和結束時間進行聯(lián)動控制的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09document.createElement()用法及注意事項(ff下不兼容)
今天處理了一個日期選擇器的ie和ff的兼容問題,本來這種情況就很難找錯誤,找了好久才把錯誤定位到js中創(chuàng)建元素的方法document.createElement(),這個方法在ie下支持這樣創(chuàng)建元素2013-03-03js中select選擇器的change事件處理函數(shù)詳解
Js操作Select是很常見的,也是比較實用的,下面這篇文章主要給大家介紹了關于js中select選擇器的change事件處理函數(shù)的相關資料,文中給出了詳細的實例代碼,需要的朋友可以參考下2023-06-06bootstrap table實現(xiàn)橫向合并與縱向合并
這篇文章主要為大家詳細介紹了bootstrap table實現(xiàn)橫向合并與縱向合并,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實例
今天小編就為大家分享一篇bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08