underscore.js 防抖設置
在實際的工作中,我們經常會遇到限制客戶多次點擊,多次滑動而重復提交代碼的過程,為了能夠有效的防止這種情況,我們今天來討論應該如何處理節流的控制。
前言
setTimeout
和clearTimeout
:
// clearTimeout 雖然取消了定時器,但是timer并沒有取消,只是如果你沒有引用,就會被垃圾回收。
let timer = setTimeout('console.log(11)')
clearTimeout(timer)
console.log(timer) // 20 一個數字
實例重現
我們現在用戶從屏幕的一端滑動另外一端,剛開始count為 1,但是最后為165,就是說,如果這里面是一個復雜的ajax請求,用戶在短短幾秒的過程中,請求了接口165次,假如每次接口的返回的時間是300ms,想想,結果是什么,結果就是用戶會被卡死,作為一個好的開發人員,我們是不是應該阻止這樣的情況發生?
<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
- 節流控制
throttle
防抖控制
上面說了,主要就是二種情況,我們今天來討論下防抖控制
原理:在一段時間內(n秒),不管用戶怎么點擊,我都不會觸發,只有等到n秒后才會執行,如果中途n秒內,用戶又再次點擊,那我就以用戶新點擊的時間開始重新計算,n秒后才執行。總之:就是在用戶觸發完事件后,n秒內不再觸發,我才執行事件,我就是任性!!!
防抖第一版
我們根據原理可以實現一個函數:
function debounce(func, wait) {
let timer
return function() {
if(timer) clearTimeout(timer)
timer = setTimeout(func, wait)
}
}
container.addEventListener(mousemove, debounce(getUserAction, 1000))
這樣處理后,在一秒內,用戶重新觸發事件的話,都不會執行,只有在最后一次觸發1s后才會觸發事件。
防抖第二版
研究一個函數的時候,我們都知道參數和this,很重要,所有這里我們也需要處理getUserAction
函數內部的this和參數。
如果我們按照第一版不處理的話,getUserAction
內部的this就是window
了,但是根據事件觸發的this規則,this應該指向事件觸發的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后,是不是要思考每一個事件處理都是有一個事件參數e,我們也需要把這個參數e傳入到getUserAction
內部,方便我們處理。
處理參數:
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秒后執行,我想立刻執行事件觸發,但是n秒內不再觸發,自己想想好像也是合理的需求。
所有我們給debounce
添加一個參數來控制是立即執行還是n秒后再執行
function debounce(func, wait, immediate) {
let timer, context
return function(...args) {
context = this
if(timer) clearTimeout(timer)
if(immediate) {
// 控制是否已經執行過
call = !timer
timer = setTimeout(() => {
timer = null
},wait)
if(call) {
func.apply(this,args)
}
} else {
timer = setTimeout(() => {
func.apply(context,args)
}, wait)
}
}
}
這樣處理后,我們就可以根據immediate
的值,來用2種方法來執行。
防抖第四版
每一個函數都有一個返回值,getUserAction
的返回值如果需要被利用呢?當不是直接執行的時候(immediate=false
)討論返回值沒有意義,一直都是undefined
(異步),所以只有立即執行的時候,才有返回值。
function debounce(func, wait, immediate) {
let timer, context, result
return function(...args) {
context = this
if(timer) clearTimeout(timer)
if(immediate) {
// 控制是否已經執行過
call = !timer
timer = setTimeout(() => {
timer = null
},wait)
if(call) {
result = func.apply(this,args)
}
} else {
timer = setTimeout(() => {
func.apply(context,args)
}, wait)
}
return result
}
}
做到這里,一個基本的防抖封裝已經完成了,一大部分的情況都可以處理了,但是有沒有想到過一種情況,現實工作中,就是讓用戶點擊一個按鈕,取消等待,繼續觸發函數?
防抖第五版
最后我們再思考一個小需求,我希望能取消 debounce 函數,比如說我 debounce 的時間間隔是 10 秒鐘,immediate 為 true,這樣的話,我只有等 10 秒后才能重新觸發事件,現在我希望有一個按鈕,點擊后,取消防抖,這樣我再去觸發,就可以又立刻執行啦,是不是很開心?
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)
恭喜你,完成了一個防抖的封裝。