underscore.js 防抖設(shè)置
在實(shí)際的工作中,我們經(jīng)常會(huì)遇到限制客戶多次點(diǎn)擊,多次滑動(dòng)而重復(fù)提交代碼的過程,為了能夠有效的防止這種情況,我們今天來討論應(yīng)該如何處理節(jié)流的控制。
前言
setTimeout和clearTimeout:
// clearTimeout 雖然取消了定時(shí)器,但是timer并沒有取消,只是如果你沒有引用,就會(huì)被垃圾回收。
let timer = setTimeout('console.log(11)')
clearTimeout(timer)
console.log(timer) // 20 一個(gè)數(shù)字
實(shí)例重現(xiàn)
我們現(xiàn)在用戶從屏幕的一端滑動(dòng)另外一端,剛開始count為 1,但是最后為165,就是說,如果這里面是一個(gè)復(fù)雜的ajax請(qǐng)求,用戶在短短幾秒的過程中,請(qǐng)求了接口165次,假如每次接口的返回的時(shí)間是300ms,想想,結(jié)果是什么,結(jié)果就是用戶會(huì)被卡死,作為一個(gè)好的開發(fā)人員,我們是不是應(yīng)該阻止這樣的情況發(fā)生?
<style>
.container {
background-color: black;
color: white;
padding: 100px 0;
text-align: center;
}
</style>
</head>
<body>
<div class="container"></div>
<script>
let count = 1
const container = document.querySelector('.container');
function getUserAction() {
container.innerHTML = count++;
return 'getUserAction'
};
container.addEventListener(mousemove, getUserAction)
</script>
</body>
解決這樣的方式一般有二種情況
- 防抖控制
debounce - 節(jié)流控制
throttle
防抖控制
上面說了,主要就是二種情況,我們今天來討論下防抖控制
原理:在一段時(shí)間內(nèi)(n秒),不管用戶怎么點(diǎn)擊,我都不會(huì)觸發(fā),只有等到n秒后才會(huì)執(zhí)行,如果中途n秒內(nèi),用戶又再次點(diǎn)擊,那我就以用戶新點(diǎn)擊的時(shí)間開始重新計(jì)算,n秒后才執(zhí)行??傊壕褪窃谟脩粲|發(fā)完事件后,n秒內(nèi)不再觸發(fā),我才執(zhí)行事件,我就是任性?。?!
防抖第一版
我們根據(jù)原理可以實(shí)現(xiàn)一個(gè)函數(shù):
function debounce(func, wait) {
let timer
return function() {
if(timer) clearTimeout(timer)
timer = setTimeout(func, wait)
}
}
container.addEventListener(mousemove, debounce(getUserAction, 1000))
這樣處理后,在一秒內(nèi),用戶重新觸發(fā)事件的話,都不會(huì)執(zhí)行,只有在最后一次觸發(fā)1s后才會(huì)觸發(fā)事件。
防抖第二版
研究一個(gè)函數(shù)的時(shí)候,我們都知道參數(shù)和this,很重要,所有這里我們也需要處理getUserAction函數(shù)內(nèi)部的this和參數(shù)。
如果我們按照第一版不處理的話,getUserAction內(nèi)部的this就是window了,但是根據(jù)事件觸發(fā)的this規(guī)則,this應(yīng)該指向事件觸發(fā)的dom元素。
加入this:
function debounce(func, wait) {
let timer, context
return function() {
context = this
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(context)
}, wait)
}
}
container.addEventListener(mousemove, debounce(getUserAction, 1000))
處理完this后,是不是要思考每一個(gè)事件處理都是有一個(gè)事件參數(shù)e,我們也需要把這個(gè)參數(shù)e傳入到getUserAction內(nèi)部,方便我們處理。
處理參數(shù):
function debounce(func, wait) {
let timer, context
return function(...args) {
context = this
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(context,args)
}, wait)
}
}
container.addEventListener(mousemove, debounce(getUserAction, 1000))
防抖第三版
突然老板來了一個(gè)新需求,我不想等n秒后執(zhí)行,我想立刻執(zhí)行事件觸發(fā),但是n秒內(nèi)不再觸發(fā),自己想想好像也是合理的需求。
所有我們給debounce添加一個(gè)參數(shù)來控制是立即執(zhí)行還是n秒后再執(zhí)行
function debounce(func, wait, immediate) {
let timer, context
return function(...args) {
context = this
if(timer) clearTimeout(timer)
if(immediate) {
// 控制是否已經(jīng)執(zhí)行過
call = !timer
timer = setTimeout(() => {
timer = null
},wait)
if(call) {
func.apply(this,args)
}
} else {
timer = setTimeout(() => {
func.apply(context,args)
}, wait)
}
}
}
這樣處理后,我們就可以根據(jù)immediate的值,來用2種方法來執(zhí)行。
防抖第四版
每一個(gè)函數(shù)都有一個(gè)返回值,getUserAction的返回值如果需要被利用呢?當(dāng)不是直接執(zhí)行的時(shí)候(immediate=false)討論返回值沒有意義,一直都是undefined(異步),所以只有立即執(zhí)行的時(shí)候,才有返回值。
function debounce(func, wait, immediate) {
let timer, context, result
return function(...args) {
context = this
if(timer) clearTimeout(timer)
if(immediate) {
// 控制是否已經(jīng)執(zhí)行過
call = !timer
timer = setTimeout(() => {
timer = null
},wait)
if(call) {
result = func.apply(this,args)
}
} else {
timer = setTimeout(() => {
func.apply(context,args)
}, wait)
}
return result
}
}
做到這里,一個(gè)基本的防抖封裝已經(jīng)完成了,一大部分的情況都可以處理了,但是有沒有想到過一種情況,現(xiàn)實(shí)工作中,就是讓用戶點(diǎn)擊一個(gè)按鈕,取消等待,繼續(xù)觸發(fā)函數(shù)?
防抖第五版
最后我們?cè)偎伎家粋€(gè)小需求,我希望能取消 debounce 函數(shù),比如說我 debounce 的時(shí)間間隔是 10 秒鐘,immediate 為 true,這樣的話,我只有等 10 秒后才能重新觸發(fā)事件,現(xiàn)在我希望有一個(gè)按鈕,點(diǎn)擊后,取消防抖,這樣我再去觸發(fā),就可以又立刻執(zhí)行啦,是不是很開心?
function debounce(func, wait, immediately) {
let timer
let debounced = function (...args) {
let result
// 清除鬧鐘后,鬧鐘還是存在的
if (timer) clearTimeout(timer)
if (immediately) {
let called = !timer
timer = setTimeout(() => {
timer = null
}, wait)
if (called) {
result = func.apply(this,args)
}
} else {
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
return result
}
debounced.cancel = function() {
clearTimeout(timer)
timer = null
}
return debounced
}
那么如何使用呢?
let action = debounce(getUserAction, 100000, true)
container.addEventListener('mousemove', action)
btn.addEventListener('click', action.cancel)
恭喜你,完成了一個(gè)防抖的封裝。