JavaScript 函數(shù)防抖和函數(shù)節(jié)流.
在瀏覽器執(zhí)行環(huán)境中,等待主隊(duì)列任務(wù)(DOM TREE
& CSS Tree
& render Tree
) 等任務(wù)執(zhí)行完畢之后.
就開(kāi)始執(zhí)行 EventLoop
環(huán)境事件了.
所以,某種程度上,瀏覽器是基于事件驅(qū)動(dòng)的
既然提到了事件,那么我們最常見(jiàn)的就是給一些元素綁定一些事件了.
比如給一個(gè)按鈕綁定一個(gè)click
事件.
給元素綁定一個(gè)事件
給元素綁定一個(gè)事件,是一個(gè)非常常見(jiàn)的場(chǎng)景.
btn.onclick = function (e) {
console.log('xxxxx')
}
這沒(méi)什么大不了的.
函數(shù)節(jié)流
先不要管什么是函數(shù)節(jié)流.
上述我們給一個(gè)按鈕添加了一個(gè)點(diǎn)擊事件.
每次點(diǎn)擊這個(gè)按鈕的時(shí)候,都會(huì)觸發(fā)這個(gè)事件響應(yīng)函數(shù).
這個(gè)是沒(méi)毛病的.
之前也都是這么做的.
但是如果,一個(gè)哥們單身30年,手速很快
假如這個(gè)哥們一秒能點(diǎn)擊100下. 且這又是一個(gè)發(fā)送ajax請(qǐng)求的按鈕.
那么,每一秒鐘
- 前端瀏覽器,這個(gè)按鈕的事件響應(yīng)函數(shù)執(zhí)行了100次.發(fā)送了100個(gè)請(qǐng)求.
- 后臺(tái)服務(wù)器每一秒接受到了100個(gè)請(qǐng)求.并處理這100個(gè)請(qǐng)求.
現(xiàn)在有一個(gè)需求是,這個(gè)按鈕一秒鐘不管點(diǎn)擊多少下,只能執(zhí)行一下.(你手速在快也沒(méi)有用)
這個(gè)需求描述的就是函數(shù)節(jié)流.
某些函數(shù)可能會(huì)出現(xiàn)非常頻繁的調(diào)用,但是在某一個(gè)周期內(nèi),不管觸發(fā)多少次,實(shí)際上只能被執(zhí)行一次.
所以需要這么一個(gè)機(jī)制:
- 一個(gè)函數(shù)被執(zhí)行了.(此時(shí)計(jì)時(shí)是A)
- 這個(gè)函數(shù)又被執(zhí)行了.(此時(shí)計(jì)時(shí)是B)
- 如果(B-A<等待值),那么這個(gè)函數(shù)就不執(zhí)行.
- 否則就執(zhí)行.
<h4>節(jié)流</h4>
<p>節(jié)流就是指函數(shù)大于等于某個(gè)時(shí)間周期才能執(zhí)行.否則就不執(zhí)行.一個(gè)周期范圍內(nèi)只能處理一次.</p>
<div id="show">0</div>
<button id='jl'>函數(shù)節(jié)流</button>
let canClick = true
let counter = 0
document.getElementById('jl').addEventListener('click', function () {
if (canClick) {
canClick = false
console.log(++counter)
setTimeout(() => {
canClick = true
}, 1000);
}
}, false)
利用一個(gè) canClick
變量來(lái)標(biāo)記這個(gè)按鈕是否被點(diǎn)擊.
并在一個(gè) setTimeout
的定時(shí)器里,定時(shí)1秒去修改這個(gè) canClick
于是,目的就達(dá)到了..每次點(diǎn)擊之后,下一次點(diǎn)擊有效必須要等待一秒.
但這樣的做法,不具備通用性.
可以封裝一個(gè)函數(shù)節(jié)流的方法,把需要節(jié)流的函數(shù)通過(guò)這個(gè)方法封裝.
function throttle (handler, wait) {
let lastTime = 0
return function () {
let nowTime = new Date().getTime()
if (nowTime - lastTime > wait) {
hanlder.apply(this,arguments)
lastTime = nowTime
}
}
}
function hanlder (e) {
e.stopPropagation()
show.innerText = parseInt(show.innerText) + 1
}
jl.addEventListener('click', throttle(hanlder, 1000), false)
函數(shù)節(jié)流:
- 這個(gè)函數(shù)可能存在頻繁執(zhí)行的情況(手速飛快,點(diǎn)擊按鈕)
- 我們希望這個(gè)函數(shù)在某個(gè)周期內(nèi)只執(zhí)行一次.
函數(shù)防抖
其實(shí)和函數(shù)節(jié)流一樣.
函數(shù)防抖也是用于解決某些函數(shù)頻繁觸發(fā)的情況.
但不同的是:
- 函數(shù)節(jié)流,指的是在某一個(gè)周期內(nèi),只能執(zhí)行一次此函數(shù)
- 函數(shù)防抖,指的是必須超過(guò)某一個(gè)時(shí)間閾值,否則函數(shù)不執(zhí)行.
一個(gè)比較常見(jiàn)的場(chǎng)景.
在文本框內(nèi)輸入搜索字符,并實(shí)時(shí)的發(fā)送搜索關(guān)鍵字到后臺(tái).
正常情況下,一般都是這么寫(xiě)的.
<input type="text" name="" id="inp">
inp.addEventListener('input', function () {
console.log(this.value)
}, false)
發(fā)現(xiàn)每次輸入一個(gè)文本都會(huì)觸發(fā)一次input
事件響應(yīng)函數(shù).
此函數(shù)的觸發(fā)頻率完全取決于用戶在輸入框中,輸入的文本快慢.
這沒(méi)有什么問(wèn)題,input
事件就是這么定義的.
但是對(duì)于實(shí)際場(chǎng)景而言,可能就出現(xiàn)了如下的不足:
- 每次文本變動(dòng),都會(huì)觸發(fā)
input
從而觸發(fā)后臺(tái)的請(qǐng)求操作. - 對(duì)于用于而言,可能需要查詢的是
123321
字符串. 而文本的輸入會(huì)導(dǎo)致之前的5次查詢都是無(wú)效的.
所以,這里就需要就一個(gè)機(jī)制..
- 事件函數(shù)不是立馬執(zhí)行.
- 會(huì)等待一段時(shí)間
- 如果在等待的這一段事件內(nèi),事件函數(shù)又被觸發(fā)了.
- 那么上一次的事件函數(shù)就不會(huì)執(zhí)行.
- 接著等待一段時(shí)間.
- 如此循環(huán)
- 直到等待時(shí)間超過(guò)了,且用戶沒(méi)有操作了.我在執(zhí)行這個(gè)事件函數(shù).
這個(gè)就是所謂的函數(shù)節(jié)流.
除非函數(shù)不觸發(fā)了,且超過(guò)某個(gè)時(shí)間閾值,否則之前的事件都不會(huì)觸發(fā).
代碼改寫(xiě)為:
let inp = document.getElementById('inp')
let timer = null
inp.addEventListener('input', function () {
clearTimeout(timer)
timer = setTimeout(() => {
console.log(this.value)
}, 1000);
}, false)
結(jié)果:
只有等待用戶操作停止了躁劣,并達(dá)到某一個(gè)等待的時(shí)間閾值,再最后觸發(fā)事件響應(yīng)函數(shù).
上述的代碼不具備通用性.
封裝函數(shù)節(jié)流方法 debounce
function debounce (hanlder, wait) {
let timer = null
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
hanlder.apply(this,arguments)
}, wait)
}
}
所以整體代碼可以改寫(xiě)為
function inputHanlder(e) {
console.log(this.value, e)
}
// 函數(shù)防抖 , 頻繁
function debounce(hanlder, wait) {
let timer = null
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
hanlder.apply(this, arguments)
}, wait);
}
}
inp.addEventListener('input',debounce(inputHanlder,1000), false)
總結(jié)
- 不管是函數(shù)防抖還是函數(shù)節(jié)流,都是為了解決函數(shù)頻繁執(zhí)行的問(wèn)題.
- 函數(shù)防抖:函數(shù)在單位時(shí)間內(nèi),只會(huì)被觸發(fā)一次.
- 函數(shù)節(jié)流:函數(shù)只有在超過(guò)了某個(gè)時(shí)間閾值后才會(huì)被執(zhí)行.否則函數(shù)不執(zhí)行.
一個(gè)只執(zhí)行一次(函數(shù)節(jié)流).
一個(gè)不滿足條件就一次也不執(zhí)行(函數(shù)防抖).