前言
在開發中經常遇到一些頻發觸發的事件,比如說鼠標移動,鍵盤的輸入輸出,窗口的縮放,滾動條的運動等等。
下面舉個例子來了解事件的頻繁觸發:
比如,頁面上有一個元素,我們給這個元素(id爲 container
)來綁定鼠標移動事件:
let count = 1
let container = document.getElementById("container")
function getUserAction(){
container.innerHTML = count++;
}
container.onmousemove = getUserAction
這是一段很簡單的代碼,效果就是隨着鼠標在元素上運動而改變元素的內容。雖然在瀏覽器上不會出現什麼卡頓,畢竟邏輯簡單啊~~如果換成複雜的邏輯或者異步操作,那麼就可怕了。
爲了解決這種問題,有兩種方案:
- 防抖
- 節流
防抖
【原理】:儘管觸發了事件,但是不立馬執行回調,如果你在一個事件觸發的 n 秒內又觸發了這個事件,那就以新的事件的事件爲準,n 秒後再執行回調,總而言之,就是要等你觸發完事件 n 秒內不觸發了再執行。
由上面可知,我們就是需要一個函數來延遲執行 DOM
的回調函數,所以立馬想到的就是 setTimeout
。
function debounce(fun,wait){
let timeout
return function(){
clearTimeout(timeout)
timeout = setTimeout(fun,wait)
}
}
所以,開頭的例子就可以使用這個防抖函數了:
container.onmousemove = debounce(getUserAction,1000)
一旦這麼使用以後,就會擾亂了 this
的指向。在事件的回調函數中,this
指向的是對應的 DOM
,也就是說,需要手動將處理真正業務邏輯的 fun
函數中的 this
指向 DOM
。
function debounce(fun,wait){
let timeout
let context = this
return function(){
clearTimeout(timeout)
timeout = setTimeout(function(){
fun.call(context)
},wait)
}
}
除了 this
以外,還需要注意的是事件對象,同樣需要修改:
function debounce(fun,wait){
let timeout
return function(){
let context = this
let args = arguments
clearTimeout(timeout)
timeout = setTimeout(function(){
fun.call(context,...arguments)
},wait)
}
}
然後,別忘了返回值:
function debounce(fun,wait){
let timeout,result
return function(){
let context = this
let args = arguments
clearTimeout(timeout)
timeout = setTimeout(function(){
result = fun.call(context,...arguments)
},wait)
return result
}
}
【總結】:
節流
【原理】:如果持續觸發事件,每隔一段時間就執行一次。
節流的實現主要有兩種方式:時間戳和定時器。
時間戳節流
當觸發事件的時候,取出當前的時間戳,然後減去之前的時間戳,如果大於設置的時間週期,就執行函數,然後更新時間戳;否則不執行。
function(fun,wait){
let previous = 0
return function(){
let now = +new Date()
let context = this
let args = arguments
if(now - previous > wait){
fun.apply(context,args)
previous = now
}
}
}
定時器節流
當觸發事件時,設置一個定時器,再觸發事件時,就判斷定時器是否存在,存在則不執行。
function(fun,wait){
let timeout
let previous
return function(){
let context = this
let args = arguments
if(!timeout){
timeout = setTimeout(function(){
timeout = null
fun.apply(context,args)
},wait)
}
}
}