居然不會函數節流跟防抖,來看這篇包學包會

前言

前端開發中會遇到一些頻繁的事件觸發,好比:window的scroll、resize;mousedown、mousemove,keyup、keydown等等,假如你對本身的代碼不作什麼的處理,你會發現頁面卡頓、觸發接口請求頻繁等問題,本文將淺析函數節流跟防抖實現,一步一步逐漸揭開函數節流跟防抖的真面目💜javascript

概念

理解防抖跟節流觸發原理,根據不一樣使用場景合理使用前端

函數防抖(debounce)

當調用動做過n毫秒後,纔會執行該動做,若在這n毫秒內又調用此動做則將從新計算執行時間,不會執行

理解原理:
儘管觸發事件,可是必定在事件觸發 n 秒後才執行,若是在一個事件觸發的 n 秒內又觸發了這個事件,就以新的事件的時間爲準,n秒後才執行,總之,就是要等觸發完事件 n 秒內再也不觸發事件,纔會執行!java

函數節流(throttle)

預先設定一個執行週期,當調用動做的時刻大於等於執行週期則執行該動做,而後進入下一個新週期

理解原理:
規定時間內,保證執行一次該函數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指向和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>segmentfault

event 對象
  • 不使用 debouce 函數,會打印 ClickEvent 對象
  • debounce 函數中,卻只會打印 undefined

解決以上問題,來更改咱們的代碼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,驗證了節流原理

小夥伴們有沒有發現此時的節流函數存在的特色
  • 時間戳會馬上執行,定時器會在 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
}

總結

節流,在規定時間內,保證執行一次該函數;防抖,當持續觸發事件時,必定時間段內沒有再觸發事件,事件處理函數纔會執行一次,若是設定的時間到來以前,又一次觸發了事件,就從新開始延時

文檔

淺談函數防抖
函數節流
函數防抖
函數節流

自我推薦

深刻源碼瞭解Vue錯誤處理
Webpack DllPlugin讓構建速度柔順絲滑

相關文章
相關標籤/搜索