js 函式防抖
阿新 • • 發佈:2019-01-05
函式防抖是什麼
函式防抖是指對於在事件被觸發n秒後再執行的回撥,如果在這n秒內又重新被觸發,則重新開始計時,是常見的優化,適用於
- 表單元件輸入內容驗證
- 防止多次點選導致表單多次提交
等情況,防止函式過於頻繁的不必要的呼叫。
程式碼實現
思路
用 setTimeout
實現計時,配合 clearTimeout
實現“重新開始計時”。
即只要觸發,就會清除上一個計時器,又註冊新的一個計時器。直到停止觸發 wait 時間後,才會執行回撥函式。
不斷觸發事件,就會不斷重複這個過程,達到防止目標函式過於頻繁的呼叫的目的。
初步實現
function debounce(func, wait) { let timeout return function () { clearTimeout(timeout) timeout = setTimeout(func, wait) //返回計時器 ID } }
示意
container.onmousemove = debounce(doSomething, 1000);
註解:關於閉包
每當事件被觸發,執行的都是那個被返回的閉包函式。
因為閉包帶來的其作用域鏈中引用的上層函式變數宣告週期延長的效果,debounce
函式的 settimeout計時器 ID timeout
變數可以在debounce
函式執行結束後依然留存在記憶體中,供閉包使用。
優化:修復
相比於未防抖時的
container.onmousemove = doSomething
防抖優化後,指向 HTMLDivElement
的從 doSomething
函式的 this
this
,前者變成了指向全域性變數。
同理,doSomething
函式引數也接收不到 MouseEvent
事件了。
修復程式碼
functiondebounce(func, wait) {let timeout returnfunction() {let context = this//傳給目標函式 clearTimeout(timeout) timeout = setTimeout( ()=>{func.apply(context, arguments)} //修復 , wait) } }
優化:立即執行
相比於 一個週期內最後一次觸發後,等待一定時間再執行目標函式;
我們有時候希望能實現 在一個週期內第一次觸發,就立即執行一次,然後一定時間段內都不能再執行目標函式。
這樣,在限制函式頻繁執行的同時,可以減少使用者等待反饋的時間,提升使用者體驗。
程式碼
在原來基礎上,新增一個是否立即執行的功能
functiondebounce(func, wait, immediate) {let time
let debounced = function() {let context = thisif(time) clearTimeout(time)
if(immediate) {
let callNow = !time
if(callNow) func.apply(context, arguments)
time = setTimeout(
()=>{time = null} //見註解
, wait)
} else {
time = setTimeout(
()=>{func.apply(context, arguments)}
, wait)
}
}
return debounced
}
註解
把儲存計時器 ID 的 time
值設定為 null
有兩個作用:
- 作為開關變數,表明一個週期結束。使得
callNow
為true
,目標函式可以在新的週期裡被觸發時被執行 timeout
作為閉包引用的上層函式的變數,是不會自動回收的。手動將其設定為 null ,讓它脫離執行環境,一邊垃圾收集器下次執行是將其回收。
優化:取消立即執行
新增一個取消立即執行的功能。
函式也是物件,也可以為其新增屬性。
為了新增 “取消立即執行”功能,為 debounced 函式添加了個 cancel 屬性,屬性值是一個函式
debounced.cancel = function() {
clearTimeout(time)
time = null
}
示意:
var setSomething = debounce(doSomething, 1000, true)
container.onmousemove = setSomething
document.getElementById("button").addEventListener('click', function(){
setSomething.cancel()
})
完整程式碼
functiondebounce(func, wait, immediate) {
let time
let debounced = function() {
let context = this
if(time) clearTimeout(time)
if(immediate) {
let callNow = !timeif(callNow) func.apply(context, arguments)
time = setTimeout(
()=>{time = null} //見註解
, wait)
} else {
time = setTimeout(
()=>{func.apply(context, arguments)}
, wait)
}
}
debounced.cancel = function() {
clearTimeout(time)
time = null
}
return debounced
}