前端開發中會遇到一些頻繁的事件觸發,好比:window的scroll、resize;mousedown、mousemove,keyup、keydown等等,假如你對本身的代碼不作什麼的處理,你會發現頁面卡頓、觸發接口請求頻繁等問題,本文將淺析函數節流跟防抖實現,一步一步逐漸揭開函數節流跟防抖的真面目💜javascript
理解防抖跟節流觸發原理,根據不一樣使用場景合理使用前端
當調用動做過n毫秒後,纔會執行該動做,若在這n毫秒內又調用此動做則將從新計算執行時間,不會執行
理解原理:
儘管觸發事件,可是必定在事件觸發 n 秒後才執行,若是在一個事件觸發的 n 秒內又觸發了這個事件,就以新的事件的時間爲準,n秒後才執行,總之,就是要等觸發完事件 n 秒內再也不觸發事件,纔會執行!java
預先設定一個執行週期,當調用動做的時刻大於等於執行週期則執行該動做,而後進入下一個新週期
理解原理:
規定時間內,保證執行一次該函數react
根據防抖原理,實現代碼以下,以後的示例都將是常規使用:git
# 箭頭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)}
經過例子你會發現,1s以內頻繁click,都會在最後輸出一次log,驗證了防抖原理github
小夥伴們有沒有發現此時的防抖函數仍存在缺陷
this指向
<div id="mecontain"></div>
segmentfault
event 對象
解決以上問題,來更改咱們的代碼app
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 秒後,再能夠從新觸發執行<br/>
經過添加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 = nullpost
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)
經過例子你會發現,頻繁click,都會在根據你設定的週期輸出一次log,驗證了節流原理
小夥伴們有沒有發現此時的節流函數存在的特色
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 }
節流,在規定時間內,保證執行一次該函數;防抖,當持續觸發事件時,必定時間段內沒有再觸發事件,事件處理函數纔會執行一次,若是設定的時間到來以前,又一次觸發了事件,就從新開始延時