js節流和防抖函數

函數防抖是指對於在事件被觸發n秒後再執行的回調,若是在這n秒內又從新被觸發,則從新開始計時,是常見的優化,適用於閉包

  • 表單組件輸入內容驗證
  • 防止屢次點擊致使表單屢次提交

等狀況,防止函數過於頻繁的沒必要要的調用。app

思路

用 setTimeout 實現計時,配合 clearTimeout 實現「從新開始計時」。函數

即只要觸發,就會清除上一個計時器,又註冊新的一個計時器。直到中止觸發 wait 時間後,纔會執行回調函數。性能

不斷觸發事件,就會不斷重複這個過程,達到防止目標函數過於頻繁的調用的目的。優化

初步實現

function debounce(func, wait) {
    let timeout
    return function () {
        clearTimeout(timeout)
        timeout = setTimeout(func, wait) //返回計時器 ID
    }
}

示例

container.onmousemove = debounce(doSomething, 1000);

註解:關於閉包

每當事件被觸發,執行的都是那個被返回的閉包函數。this

由於閉包帶來的其做用域鏈中引用的上層函數變量聲明週期延長的效果,debounce 函數的 settimeout計時器 ID timeout 變量能夠在debounce 函數執行結束後依然留存在內存中,供閉包使用。spa

優化:修復

相比於未防抖時的code

container.onmousemove = doSomething 

防抖優化後,指向 HTMLDivElement 的從 doSomething 函數的 this 變成了閉包匿名函數的 this ,前者變成了指向全局變量。 
同理,doSomething 函數參數也接收不到 MouseEvent 事件了。對象

修復代碼

function debounce(func, wait) {
    let timeout
    return function () {
        let context = this //傳給目標函數
        clearTimeout(timeout)
        timeout = setTimeout(
            ()=>{func.apply(context, arguments)} //修復
        , wait)
    }
}

優化:當即執行

相比於 一個週期內最後一次觸發後,等待必定時間再執行目標函數;blog

咱們有時候但願能實現 在一個週期內第一次觸發,就當即執行一次,而後必定時間段內都不能再執行目標函數。

這樣,在限制函數頻繁執行的同時,能夠減小用戶等待反饋的時間,提高用戶體驗。

代碼

在原來基礎上,添加一個是否當即執行的功能

function debounce(func, wait, immediate) {
    let time
    let debounced = function() {
        let context = this
        if(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()
})

完整代碼

function debounce(func, wait, immediate) {
    let time
    let debounced = function() {
        let context = this
        if(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) 
        }
    }

    debounced.cancel = function() {
        clearTimeout(time)
        time = null
    }

    return debounced
}

 

 

Vue.js中的解決方案

首先在公共函數文件中註冊debounce

export function debounce(func, delay) {
  let timer

  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

而後在須要使用的組件中引入debounce,而且在created生命週期內調用:

created() {
  this.$watch('searchText', debounce((newSearchText) => {
    this.getDatas(newSearchText)
  }, 200))
}

 

 

節流:

一個函數執行一次後,只有大於設定的執行週期,纔會執行第二次

//節流函數
    function throttle(fn, delay) {
      //記錄上一次函數觸發的時間
      var lastTime = 0;
      console.log(this)
      return function () {
        //記錄當前函數觸發的時間
        var nowTime = Date.now();
        if (nowTime - lastTime > delay) {
          //修正this的指向
          fn.call(this);
          console.log(this);
          lastTime = nowTime;
        }
      }
    }
    document.onscroll = throttle(function () {
      console.log('觸發了scroll' + Date.now())
    }, 200)

 

防抖:

有個須要頻繁觸發函數,出於優化性能角度,在規定的時間內,只讓函數觸發的第一次生效,後面的不生效。

function debounce(fn,delay) {
      //記錄上一次的延時器
      var timer = null;
      return function () {
        clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(this);
        }, delay);
      }
    }
相關文章
相關標籤/搜索