underscore.js 防抖設(shè)置
在實際的工作中,我們經(jīng)常會遇到限制客戶多次點擊,多次滑動而重復(fù)提交代碼的過程,為了能夠有效的防止這種情況,我們今天來討論應(yīng)該如何處理節(jié)流的控制。
前言
setTimeout和clearTimeout:
// clearTimeout 雖然取消了定時器,但是timer并沒有取消,只是如果你沒有引用,就會被垃圾回收。
let timer = setTimeout('console.log(11)')
clearTimeout(timer)
console.log(timer) // 20 一個數(shù)字
實例重現(xiàn)
我們現(xiàn)在用戶從屏幕的一端滑動另外一端,剛開始count為 1,但是最后為165,就是說,如果這里面是一個復(fù)雜的ajax請求,用戶在短短幾秒的過程中,請求了接口165次,假如每次接口的返回的時間是300ms,想想,結(jié)果是什么,結(jié)果就是用戶會被卡死,作為一個好的開發(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
防抖控制
上面說了,主要就是二種情況,我們今天來討論下防抖控制
原理:在一段時間內(nèi)(n秒),不管用戶怎么點擊,我都不會觸發(fā),只有等到n秒后才會執(zhí)行,如果中途n秒內(nèi),用戶又再次點擊,那我就以用戶新點擊的時間開始重新計算,n秒后才執(zhí)行??傊壕褪窃谟脩粲|發(fā)完事件后,n秒內(nèi)不再觸發(fā),我才執(zhí)行事件,我就是任性!??!
防抖第一版
我們根據(jù)原理可以實現(xiàn)一個函數(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ā)事件的話,都不會執(zhí)行,只有在最后一次觸發(fā)1s后才會觸發(fā)事件。
防抖第二版
研究一個函數(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后,是不是要思考每一個事件處理都是有一個事件參數(shù)e,我們也需要把這個參數(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))
防抖第三版
突然老板來了一個新需求,我不想等n秒后執(zhí)行,我想立刻執(zhí)行事件觸發(fā),但是n秒內(nèi)不再觸發(fā),自己想想好像也是合理的需求。
所有我們給debounce添加一個參數(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í)行。
防抖第四版
每一個函數(shù)都有一個返回值,getUserAction的返回值如果需要被利用呢?當不是直接執(zhí)行的時候(immediate=false)討論返回值沒有意義,一直都是undefined(異步),所以只有立即執(zhí)行的時候,才有返回值。
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
}
}
做到這里,一個基本的防抖封裝已經(jīng)完成了,一大部分的情況都可以處理了,但是有沒有想到過一種情況,現(xiàn)實工作中,就是讓用戶點擊一個按鈕,取消等待,繼續(xù)觸發(fā)函數(shù)?
防抖第五版
最后我們再思考一個小需求,我希望能取消 debounce 函數(shù),比如說我 debounce 的時間間隔是 10 秒鐘,immediate 為 true,這樣的話,我只有等 10 秒后才能重新觸發(fā)事件,現(xià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)
恭喜你,完成了一個防抖的封裝。