前言
前端開發中會遇到一些頻繁的事件觸發,比如:window的scroll、resize;mousedown、mousemove,keyup、keydown等等,假如你對自己的程式碼不做什麼的處理,你會發現頁面卡頓、觸發介面請求頻繁等問題,本文將淺析函式節流跟防抖實現,一步一步逐漸揭開函式節流跟防抖的真面目?
概念
理解防抖跟節流觸發原理,根據不同使用場景合理使用
函式防抖(debounce)
當呼叫動作過n毫秒後,才會執行該動作,若在這n毫秒內又呼叫此動作則將重新計算執行時間,不會執行
複製程式碼
理解原理:
儘管觸發事件,但是一定在事件觸發 n 秒後才執行,如果在一個事件觸發的 n 秒內又觸發了這個事件,就以新的事件的時間為準,n秒後才執行,總之,就是要等觸發完事件 n 秒內不再觸發事件,才會執行!
函式節流(throttle)
預先設定一個執行週期,當呼叫動作的時刻大於等於執行週期則執行該動作,然後進入下一個新週期
複製程式碼
理解原理:
規定時間內,保證執行一次該函式
實現
防抖
根據防抖原理,實現程式碼如下,之後的示例都將是常規使用:
# 箭頭log函式
let count = 0
const log = () => {
console.log(this)
++count
console.log(count)
}
# 函式表示式
const log = function (evt) {
console.log(this)
console.log(evt)
++count
console.log(count)
}
複製程式碼
const debounce = function (fn, delay){
let timeout = null
return function () {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(fn, delay)
}
}
複製程式碼
使用頻繁click事件為例
常規使用: meContain.onclick = debounce(log, 1000)
react demo: ...onClick={debounce(log.bind(this), 1000)}
小夥伴們有沒有發現此時的防抖函式仍存在缺陷
- this指向和event 物件
- 假如使用者現在一直點選提交按鈕的話,就會一直不發出請求,也得不到任何提示,對使用者體驗相當不好
this指向和event 物件
this指向
- log函式中 console.log(this),不使用 debounce 函式的時候,this 的值為:undefined, 這是因為使用了箭頭函式,此時需要onClick呼叫的時候bind(this), this指向react元件示例
- 常規使用中console.log(this),不使用 debounce 函式的時候,this 的值為:
<div id="mecontain"></div>
event 物件
- 不使用 debouce 函式,會列印 ClickEvent 物件
- debounce 函式中,卻只會列印 undefined
解決以上問題,來更改我們的程式碼
const debounce = function (fn, delay){
let timeout = null
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
複製程式碼
互動優化體驗
如果希望立刻執行函式一次,不用等到事件停止觸發後才執行,然後等到停止觸發 n 秒後,再可以重新觸發執行
通過新增isImmeDiate來判斷是否立刻執行
const debounce = function (fn, delay,isImmeDiate= false){
let timeout = null
return function () {
const context = this;
const args = arguments;
if(timeout) clearTimeout(timeout)
if(isImmeDiate) {
# 判斷是否已經執行過,不要重複執行
let callNow = !timeout
timeout = setTimeout(function(){
timeout = null;
}, delay)
if(callNow) result = fn.apply(context, args)
} else {
timeout = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
return result
}
}
複製程式碼
如果要新增一個取消debounce開關,只需要新增一個cancle函式清除定時器timeout = null
const debounce = function (fn, delay,isImmeDiate= false){
let timeout = null
const debounced = function () {
const context = this;
const args = arguments;
if(timeout) clearTimeout(timeout)
if(isImmeDiate) {
# 判斷是否已經執行過,不要重複執行
# setTimeout也是一直在更新的
let callNow = !timeout
timeout = setTimeout(function(){
timeout = null;
}, delay)
if(callNow) result = fn.apply(context, args)
} else {
timeout = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
return result
}
debounced.prototype.cancle = function() {
clearTimeout(timeout)
timeout = null
}
return debounced
}
複製程式碼
到這裡我們實現了一個防抖函式,但是小夥伴們有沒有別的想法呢?
節流
根據節流原理,實現程式碼如下,之後的示例都將是常規使用:
時間戳實現
const throttle = function (fn, delay) {
let preTime = 0;
return function () {
const context = this;
const args = arguments;
const now = +new Date();
if (now - preTime > delay) {
fn.apply(context, args);
preTime = now;
}
}
}
複製程式碼
定時器實現
const throttle = function (fn, delay) {
let timeout = null
return function () {
const context = this;
const args = arguments;
if (!timeout) {
timeout = setTimeout(function(){
timeout = null
fn.apply(context, args)
}, delay)
}
}
}
# 如果需要立刻執行,其實變更下執行順序即可
timeout = setTimeout(function(){
timeout = null
//fn.apply(context, args)
}, delay)
fn.apply(context, args)
複製程式碼
同樣使用頻繁click事件為例
常規使用: meContain.onclick = throttle(log, 1000)
小夥伴們有沒有發現此時的節流函式存在的特點
- 時間戳會立刻執行,定時器會在 n 秒後第一次執行
- 時間戳停止觸發後沒有辦法再執行事件,定時器實現停止觸發後依然會再執行一次事件
合併兩者特點
const throttle = function (fn, delay) {
let timeout = null
let preTime = 0;
const later = function() {
preTime = +new Date()
timeout = null
fn.apply(context, args);
}
const throttled = function () {
const context = this;
const args = arguments;
const now = +new Date();
#下次觸發fn剩餘的時間
const remaining = delay - ( now - preTime)
#如果沒有剩餘的時間了或者系統時間變更
if (remaining <= 0 || remaining > delay) {
if(timeout) {
clearTimeout(timeout)
timeout = null
}
preTime = now
fn.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining)
}
}
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
return throttled
}
複製程式碼
總結
節流,在規定時間內,保證執行一次該函式;防抖,當持續觸發事件時,一定時間段內沒有再觸發事件,事件處理函式才會執行一次,如果設定的時間到來之前,又一次觸發了事件,就重新開始延時