函數防抖和節流

在前端開發的過程當中,咱們常常會須要綁定一些持續觸發的事件,如 resize、scroll、mousemove 等等,但有些時候咱們並不但願在事件持續觸發的過程當中那麼頻繁地去執行函數。html

一般這種狀況下咱們怎麼去解決的呢?通常來說,防抖和節流是比較好的解決方案。前端

讓咱們先來看看在事件持續觸發的過程當中頻繁執行函數是怎樣的一種狀況。git

html 文件中代碼以下github

<div id="content"
    style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
  <script>
    let num = 1;
    const content = document.getElementById('content');
    function count() {
      content.innerHTML = num++;
    };
    content.onmousemove = count;
  </script>
複製代碼

在上述代碼中,div 元素綁定了 mousemove 事件,當鼠標在 div(灰色)區域中移動的時候會持續地去觸發該事件致使頻繁執行函數。效果以下bash

能夠看到,在沒有經過其它操做的狀況下,函數被頻繁地執行致使頁面上數據變化特別快。因此,接下來讓咱們來看看防抖和節流是如何去解決這個問題的。markdown

防抖(debounce)

所謂防抖,就是指觸發事件後 n 秒後才執行函數,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間。app

防抖函數分爲非當即執行版和當即執行版。函數

非當即執行版:oop

function debounce(func, wait) {
  let timeout;
  return function () {
    const context = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args)
    }, wait);
  }
}
複製代碼

非當即執行版的意思是觸發事件後函數不會當即執行,而是在 n 秒後執行,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間。this

咱們依舊使用上述綁定 mousemove 事件的例子,經過上面的防抖函數,咱們能夠這麼使用

content.onmousemove = debounce(count,1000);
複製代碼

效果以下

能夠看到,在觸發事件後函數 1 秒後才執行,而若是我在觸發事件後的 1 秒內又觸發了事件,則會從新計算函數執行時間。

上述防抖函數的代碼還須要注意的是 this 和 參數的傳遞

const context = this;
const args = [...arguments];
複製代碼

防抖函數的代碼使用這兩行代碼來獲取 this 和 參數,是爲了讓 debounce 函數最終返回的函數 this 指向不變以及依舊能接受到 e 參數。

當即執行版:

function debounce(func,wait) {
  let timeout;
  return function () {
      const context = this;
      const args = [...arguments];
      if (timeout) clearTimeout(timeout);
      const callNow = !timeout;
      timeout = setTimeout(() => {
          timeout = null;
      }, wait)
      if (callNow) func.apply(context, args)
  }
}
複製代碼

當即執行版的意思是觸發事件後函數會當即執行,而後 n 秒內不觸發事件才能繼續執行函數的效果。

使用方法同上,效果以下

在開發過程當中,咱們須要根據不一樣的場景來決定咱們須要使用哪個版本的防抖函數,通常來說上述的防抖函數都能知足大部分的場景需求。但咱們也能夠將非當即執行版和當即執行版的防抖函數結合起來,實現最終的雙劍合璧版的防抖函數。

雙劍合璧版:

/**
 * @desc 函數防抖
 * @param func 函數
 * @param wait 延遲執行毫秒數
 * @param immediate true 表當即執行,false 表非當即執行
 */
function debounce(func, wait, immediate) {
  let timeout;
  return function () {
    const context = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
      if (callNow) func.apply(context, args)
    }
    else {
      timeout = setTimeout(() => {
        func.apply(context, args)
      }, wait);
    }
  }
}
複製代碼

節流(throttle)

所謂節流,就是指連續觸發事件可是在 n 秒中只執行一次函數。 節流會稀釋函數的執行頻率。

對於節流,通常有兩種方式能夠實現,分別是時間戳版和定時器版。

時間戳版:

function throttle(func, wait) {
    var previous = 0;
    return function() {
        let now = Date.now();
        let context = this;
        let args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}
複製代碼

使用方式以下

content.onmousemove = throttle(count,1000);
複製代碼

效果以下

能夠看到,在持續觸發事件的過程當中,函數會當即執行,而且每 1s 執行一次。

定時器版:

function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}
複製代碼

使用方式同上,效果以下

能夠看到,在持續觸發事件的過程當中,函數不會當即執行,而且每 1s 執行一次,在中止觸發事件後,函數還會再執行一次。

咱們應該能夠很容易的發現,其實時間戳版和定時器版的節流函數的區別就是,時間戳版的函數觸發是在時間段內開始的時候,而定時器版的函數觸發是在時間段內結束的時候。

一樣地,咱們也能夠將時間戳版和定時器版的節流函數結合起來,實現雙劍合璧版的節流函數。

雙劍合璧版:

/**
 * @desc 函數節流
 * @param func 函數
 * @param wait 延遲執行毫秒數
 * @param type 1 表時間戳版,2 表定時器版
 */
function throttle(func, wait ,type) {
    if(type===1){
        let previous = 0;
    }else if(type===2){
        let timeout;
    }
    return function() {
        let context = this;
        let args = arguments;
        if(type===1){
            let now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }

    }
}
複製代碼

參考文章:

github.com/mqyqingfeng…

github.com/mqyqingfeng…

相關文章
相關標籤/搜索