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