欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

利用JavaScript實現(xiàn)防抖節(jié)流函數(shù)的示例代碼

 更新時間:2022年08月18日 09:46:39   作者:pino  
在開發(fā)中我們經(jīng)常會遇到一些高頻操作,比如:鼠標移動,滑動窗口,鍵盤輸入等等,節(jié)流和防抖就是對此類事件進行優(yōu)化,降低觸發(fā)的頻率,以達到提高性能的目的。本文就教大家如何實現(xiàn)一個讓面試官拍大腿的防抖節(jié)流函數(shù),需要的可以參考一下

最近在看紅樓夢,看的詩詞多了,時不時的也想來一句...

這幾天剛看看到了underscore.js的防抖和節(jié)流的部分,正好又去復(fù)習(xí)了這部分內(nèi)容,于是又重新整理一下相關(guān)的知識點。

在開發(fā)中我們經(jīng)常會遇到一些高頻操作,比如:鼠標移動,滑動窗口,鍵盤輸入等等,節(jié)流和防抖就是對此類事件進行優(yōu)化,降低觸發(fā)的頻率,以達到提高性能的目的。

可以看到短短的幾秒鐘,觸發(fā)的事件的次數(shù)是非常驚人的。

防抖

簡單來說防抖就是無論觸發(fā)多少次事件,但是我一定在事件觸發(fā)后 n 秒后才執(zhí)行,也就是最后一次觸發(fā)完畢 n 秒后才執(zhí)行,如果在 n 秒前又觸發(fā)了,那么以新的事件的時間為準,重新開始計算時間。

那么如何實現(xiàn)一個基本的防抖函數(shù)呢?

基本實現(xiàn)

根據(jù)防抖的原理可知,我們可以設(shè)置一個定時器,當每次觸發(fā)事件但是沒有到達設(shè)置的時間時,都會重新設(shè)置定時器。

 const debounce = function(func, wait) {
   let timeout
   return function() {
     // 再次觸發(fā)事件則刪除上一個定時器,重新設(shè)置
     clearTimeout(timeout)
     timeout = setTimeout(func, wait);
   }
 }

這樣我們就寫出了一個最基本版的防抖函數(shù)。可以看到觸發(fā)次數(shù)已經(jīng)大大降低。

this & arguments

盡管上面已經(jīng)實現(xiàn)了一個基本的防抖函數(shù),但是依然是不完善的,比如在setTimeout中的this指向是無法正確的獲取的,setTimeout中的this指向 Window 對象!

我們可以在執(zhí)行定時器之前進行重置this

 const debounce = function(func, wait) {
   let timeout
   return function() {
     // 保存this
     let context = this // 新增
 
     clearTimeout(timeout)
     timeout = setTimeout(function() {
       func.apply(context) // 新增
     }, wait);
   }
 }

再比如我們?nèi)绾卧谧远x的函數(shù)進行傳參呢,如果我們想在func函數(shù)中傳遞event對象,目前的實現(xiàn)顯然是無法正確進行獲取參數(shù)的,再來修改一下:

 const debounce = function(func, wait) {
   let timeout
   return function() {
     let context = this // 新增
     // 保存參數(shù)
     let args = arguments // 新增
 
     clearTimeout(timeout)
     timeout = setTimeout(function() {
       func.apply(context, args) // 修改
     }, wait);
   }
 }

至此一個基本的防抖函數(shù)就已經(jīng)實現(xiàn)了,這個函數(shù)已經(jīng)很是非常完善了。

立即執(zhí)行

接下來再增加一個功能,如果我們不希望非要等到事件停止觸發(fā)后才執(zhí)行,希望立刻執(zhí)行函數(shù),然后等到停止觸發(fā) n 秒后,才重新觸發(fā)執(zhí)行。

那么這個功能怎么做呢,其實可以這樣想,我們可以傳入一個參數(shù)immediate,代表是否想要立即執(zhí)行,如果傳遞了immediate,則立即執(zhí)行一次函數(shù),然后設(shè)置一個定時器,時間截止后將定時器設(shè)置為null,下次進入函數(shù)時先判斷定時器是否為null,然后決定是否再次執(zhí)行。

 const debounce = function(func, wait, immediate) {
   let res, timeout, context, args;
 
   const debounced = function() {
     context = this
     args = arguments
     // 如果已經(jīng)設(shè)置了setTimeout,則重新進行設(shè)置
     if(timeout) clearTimeout(timeout)
     // 判斷是否為立即執(zhí)行
     if(immediate) {
       let runNow = !timeout
       // 設(shè)置定時器,指定時間后設(shè)置為null
       timeout = setTimeout(function() {
         timeout = null
       }, wait)
       // 如果timeout已經(jīng)為null(已到期),則執(zhí)行函數(shù)
       // 保存執(zhí)行結(jié)果,用于函數(shù)返回
       if(runNow) res = func.apply(context, args)
     } else {
       // 如果沒有設(shè)置立即執(zhí)行,則設(shè)置定時器
       timeout = setTimeout(function() {
         func.apply(context, args)
       }, wait)
     }
     return res
   }
 
   return debounced
 }

其實上面的實現(xiàn)是兩種完全不同的觸發(fā)方式,先來看一下流程圖:

黑色箭頭為觸發(fā)動作,紅色箭頭為執(zhí)行動作。

非立即執(zhí)行

立即執(zhí)行

來看一下執(zhí)行流程: 首先如果immediate為true的情況:

第一次執(zhí)行:timeoutnull,則runNowtrue,然后設(shè)置一個定時器,在指定的時間后設(shè)置timeoutnull,這也就代表設(shè)置執(zhí)行的間隔時間,最后判斷runNow是否執(zhí)行函數(shù)。

第二次執(zhí)行:

  • 情況一:已超過設(shè)置時間:如果第二次觸發(fā)執(zhí)行已經(jīng)超過設(shè)置的時間,此時timeout已經(jīng)被定時器設(shè)置為null,那么進入debounced函數(shù)后,runNowtrue,重新設(shè)置定時器,然后執(zhí)行函數(shù)。
  • 情況二:未超過設(shè)置時間:因為沒有超過設(shè)置時間,所以timeout并未被定時器設(shè)置為null,那么runNowfalse,由于timeout的定時器已經(jīng)被清除,所以重置定時器,不會執(zhí)行函數(shù)。

再來看一下immediatefalse的情況:

其實這種情況和我們之前設(shè)置的是一樣的,沒有超過設(shè)置時間,則重置定時器,定時器在到達指定時間后自動執(zhí)行一次函數(shù)。

兩者之間最大的區(qū)別是:立即執(zhí)行的功能會在第一次觸發(fā)函數(shù)的時候執(zhí)行一次,下次觸發(fā)如果已到達設(shè)置時間,則直接執(zhí)行一次。而非立即執(zhí)行的功能第一次觸發(fā)函數(shù)時只會設(shè)置一個定時器,時間到達后自動執(zhí)行,如果在設(shè)置時間內(nèi)觸發(fā)只會重置定時器,永遠不會立即執(zhí)行函數(shù)。

取消

再增加一個需求:如果想要取消debounce函數(shù)怎么辦,比如 debounce 的時間間隔是 10 秒鐘,immediatetrue,這樣只有等 10 秒后才能重新觸發(fā)事件,如果有一個取消功能,點擊后取消防抖,再去觸發(fā),就可以立刻執(zhí)行了。

 debounced.cancel = function() {
     // 刪除定時器
     clearTimeout(timeout);
     // 設(shè)置timeout為null
     timeout = null;
 };

只需要將定時器清除,設(shè)置timeoutnull即可,因為如果immediatetrue會直接執(zhí)行一次函數(shù),然后重新設(shè)置定時器 

完整實現(xiàn)

最后完整的防抖函數(shù)如下:

 function debounce(func, wait, immediate) {
   let res, timeout, context, args;
 
   const debounced = function () {
       context = this;
       args = arguments;
 
       if (timeout) clearTimeout(timeout);
       if (immediate) {
           var runNow = !timeout;
           timeout = setTimeout(function(){
               timeout = null;
           }, wait)
           if (runNow) res = func.apply(context, args)
       }
       else {
           timeout = setTimeout(function(){
               func.apply(context, args)
           }, wait);
       }
       return res;
   };
 
   debounced.cancel = function() {
       clearTimeout(timeout);
       timeout = null;
   };
 
   return debounced;
 }

節(jié)流

節(jié)流也是用于減少觸發(fā)執(zhí)行的手段之一,但是思路和防抖是完全不一樣的,

如果持續(xù)觸發(fā)事件,每隔一段時間,只執(zhí)行一次事件。也就是只按照設(shè)置的時間作為時間段,到達指定的時間后觸發(fā)函數(shù)就會執(zhí)行。沒有到達指定的時間,無論如何觸發(fā)函數(shù)都不會執(zhí)行。

也就是沒到點,無論你怎么撩,我都巋然不動 

目前有兩種實現(xiàn)方式:使用時間戳和設(shè)置定時器。

時間戳

當觸發(fā)函數(shù)的時候,使用當前的時間戳與上一次觸發(fā)函數(shù)所保存的時間戳相減,然后對比設(shè)置定時器的時間,決定是否執(zhí)行函數(shù)。

 const throttle = function(func, wait) {
   let previous = 0, context, args;
 
   return function() {
     context = this
     args = arguments
 
     // 獲取當前時間戳
     let now = +new Date()
     // 判斷當前時間戳與上一次觸發(fā)的時間差值是否大于等于指定時間
     if((now - previous) >= wait) {
       func.apply(context, args)
       // 更新時間戳
       previous = now
     }
   }
 }

值得注意的是:js中可以在某個元素前使用 '+' 號,這個操作是將該元素轉(zhuǎn)換成Number類型,如果轉(zhuǎn)換失敗,那么將得到 NaN

+new Date() 將會調(diào)用 Date.prototype 上的 valueOf() 方法,根據(jù)MDN,Date.prototype.value方法等同于Date.prototype.getTime()。

 console.log(+new Date('2022-08-17'));
 console.log(new Date('2022-08-17').getTime());
 console.log(new Date('2022-08-17').valueOf());
 console.log(new Date('2022-08-17') * 1);
 // 結(jié)果都是相同的

設(shè)置定時器

設(shè)置定時器的實現(xiàn)思路是:在第一次觸發(fā)時設(shè)置一個定時器,在指定時間之后設(shè)置變量為null,下次觸發(fā)函數(shù)判斷變量是否為null,來決定是否執(zhí)行函數(shù)。

const throttle = function(func, wait) {
  let timeout, context, args;

  return function() {
    context = this
    args = arguments
    // 允許執(zhí)行
    if(!timeout) {
      // 設(shè)置定時器,到達時間后設(shè)置timeout為null
      timeout = setTimeout(function() {
        timeout = null
        func.apply(context, args)
      }, wait)
    }
  }
}

以上兩種方式均可以滿足一個基本的節(jié)流函數(shù)的寫法,但是兩種寫法還是有一定的區(qū)別的:

  • 第一種事件會立刻執(zhí)行,第二種事件會在 n 秒后第一次執(zhí)行
  • 第一種事件停止觸發(fā)后不會再執(zhí)行事件,第二種事件停止觸發(fā)后依然會再執(zhí)行一次事件

既然執(zhí)行時的行為不同,那么有沒有辦法將兩者結(jié)合呢?

兩者結(jié)合

將兩者結(jié)合起來是要實現(xiàn)一個既能開始時執(zhí)行一次函數(shù),又能結(jié)束時再執(zhí)行一次函數(shù)!

思路是這樣的:如果觸發(fā)函數(shù)時沒有到達指定時間,則設(shè)置定時器,如果已經(jīng)到達設(shè)置的時間,則直接進行執(zhí)行。

function throttle(func, wait) {
  let timeout, context, args, previous = 0;

  const later = function() {
      // 定時器執(zhí)行時更新時間戳
      previous = +new Date();
      timeout = null;
      // 執(zhí)行函數(shù)
      func.apply(context, args)
  };

  const throttled = function() {
      let now = +new Date();
      //下次觸發(fā) func 剩余的時間
      let remaining = wait - (now - previous);
      context = this;
      args = arguments;
      // 如果沒有剩余的時間了或者更改了系統(tǒng)時間
      if (remaining <= 0 || remaining > wait) {
          // 清空定時器及timeout
          if (timeout) {
              clearTimeout(timeout);
              timeout = null;
          }
          // 更新時間戳變量
          previous = now;
          func.apply(context, args);
      } else if (!timeout) {
          // 處理還沒有到達指定時間的觸發(fā)行為
          // 此處設(shè)置定時器時間要設(shè)置剩余的時間,與上文中防抖函數(shù)中有區(qū)別
          timeout = setTimeout(later, remaining);
      }
  };
  return throttled;
}

還是依舊縷一下思路:

第一次觸發(fā) throttled 時,因為 previous 為 0 ,所以remaining <= 0這個條件成立,執(zhí)行func函數(shù),并且重置定時器及變量,最后將previous跟更新為當前時間。

第二次觸發(fā):

  • 未到達指定時間:如果沒有到達指定時間,那么remaining為正數(shù),所以不會進入remaining <= 0這個執(zhí)行語句,而是會設(shè)置定時器。不會執(zhí)行函數(shù)。
  • 到達指定時間:remaining為負數(shù),執(zhí)行函數(shù),同第一次觸發(fā)。

同樣在定時器執(zhí)行時,也會更新previoustimeout的值。

其實核心在于remaining這個變量的運算。

控制執(zhí)行時機

又又又來了一個需求,如果希望能夠控制首次和末次要不要執(zhí)行怎么辦?

可以傳遞第三個參數(shù):

  • leading:false 表示禁用第一次執(zhí)行
  • trailing: false 表示禁用停止觸發(fā)的回調(diào)
function throttle(func, wait, options = {}) { //修改
  let timeout, context, args, previous = 0;

  const later = function() {
      previous = options.leading === false ? 0 : +new Date(); //修改
      timeout = null;
      func.apply(context, args);
      // 清空作用域及參數(shù)變量
      if (!timeout) context = args = null; //修改
  };

  const throttled = function() {
      let now = +new Date();
      // 如果是首次觸發(fā),并且設(shè)置首次不執(zhí)行函數(shù)。那么將previous與now進行同步
      // now 與 previous 相減不小于0,則不會執(zhí)行函數(shù)
      if (!previous && options.leading === false) previous = now; // 新增
      let remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
          if (timeout) {
              clearTimeout(timeout);
              timeout = null;
          }
          previous = now;
          func.apply(context, args);
          // 清空作用域及參數(shù)變量
          if (!timeout) context = args = null; //修改
      } else if (!timeout && options.trailing !== false) { // 修改
          timeout = setTimeout(later, remaining);
      }
  };
  return throttled;
}

我們要注意的是實現(xiàn)中有這樣一個問題:

那就是 leading:falsetrailing: false 不能同時設(shè)置。因為如果同時設(shè)置,那么就是既不開始觸發(fā)也不結(jié)束時觸發(fā),那么函數(shù)將不會正常執(zhí)行。

其實核心還是關(guān)于時間戳的加減法,無非就是根據(jù)功能來設(shè)置時間戳而已。

取消

與防抖函數(shù)的取消功能基本相同,重置各個作用變量:

throttled.cancel = function() {
    clearTimeout(timeout);
    previous = 0;
    timeout = null;
}

完整實現(xiàn)

function throttle(func, wait, options = {}) {
  let timeout, context, args, previous = 0;

  const later = function() {
      previous = options.leading === false ? 0 : +new Date();
      timeout = null;
      func.apply(context, args);
      if (!timeout) context = args = null; 
  };

  const throttled = function() {
      let now = +new Date();
      if (!previous && options.leading === false) previous = now;
      let remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
          if (timeout) {
              clearTimeout(timeout);
              timeout = null;
          }
          previous = now;
          func.apply(context, args);
          if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
          timeout = setTimeout(later, remaining);
      }

      throttled.cancel = function() {
          clearTimeout(timeout);
          previous = 0;
          timeout = null;
      }
  };
  return throttled;
}

這也是underscore.js中節(jié)流的實現(xiàn)方式。

以上就是利用JavaScript實現(xiàn)防抖節(jié)流函數(shù)的示例代碼的詳細內(nèi)容,更多關(guān)于JavaScript防抖節(jié)流函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • javascript利用鍵盤控制小方塊的移動

    javascript利用鍵盤控制小方塊的移動

    這篇文章主要為大家詳細介紹了javascript利用鍵盤控制小方塊的移動,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • 利用javascript實現(xiàn)的三種圖片放大鏡效果實例(附源碼)

    利用javascript實現(xiàn)的三種圖片放大鏡效果實例(附源碼)

    這篇文章主要介紹了利用javascript實現(xiàn)的幾種放大鏡效果,很實用一款漂亮的js圖片放大鏡特效,常見于電商網(wǎng)站上產(chǎn)品頁,用來放大展示圖片細節(jié),很有實用性,推薦下載學(xué)習(xí)研究。文中提供了完整的源碼供大家下載,需要的朋友可以參考借鑒,一起來看看吧。
    2017-01-01
  • 微信小程序block的使用教程

    微信小程序block的使用教程

    這篇文章主要介紹了微信小程序block的使用 ,微信小程序最近非?;馃?,實現(xiàn)起來也很簡單,只要block就可以實現(xiàn),需要的朋友可以參考下
    2018-04-04
  • JavaScript斷言與類型守衛(wèi)及聯(lián)合聲明超詳細介紹

    JavaScript斷言與類型守衛(wèi)及聯(lián)合聲明超詳細介紹

    這篇文章主要介紹了JavaScript斷言與類型守衛(wèi)及聯(lián)合聲明,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-11-11
  • JavaScript 原型鏈學(xué)習(xí)總結(jié)

    JavaScript 原型鏈學(xué)習(xí)總結(jié)

    在JavaScript中,一切都是對像,函數(shù)是第一型
    2010-10-10
  • 一文詳解preact的高性能狀態(tài)管理Signals

    一文詳解preact的高性能狀態(tài)管理Signals

    這篇文章主要介紹了一文詳解preact的高性能狀態(tài)管理Signals,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,感興趣的朋友可以參考一下
    2022-09-09
  • JS兩種類型的表單提交方法實例分析

    JS兩種類型的表單提交方法實例分析

    這篇文章主要介紹了JS兩種類型的表單提交方法,結(jié)合實例形式分析了2種常用的表單提交驗證的實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2016-11-11
  • JavaScript實現(xiàn)谷歌瀏覽器插件開發(fā)的方法詳解

    JavaScript實現(xiàn)谷歌瀏覽器插件開發(fā)的方法詳解

    對于瀏覽器插件相信大家都不陌生,誰的瀏覽器不裝幾個好用的插件呢,更是有油猴這個強大的神器。所以本文就來用JavaScript開發(fā)一個谷歌瀏覽器插件,感興趣的小伙伴可以了解一下
    2022-11-11
  • 利用Three.js如何實現(xiàn)陰影效果實例代碼

    利用Three.js如何實現(xiàn)陰影效果實例代碼

    使用three.js可以方便的讓我們在網(wǎng)頁中做出各種不同的3D效果,下面這篇文章主要給大家介紹了關(guān)于利用Three.js如何實現(xiàn)陰影效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-09-09
  • 實現(xiàn)點擊列表彈出列表索引的兩種方式

    實現(xiàn)點擊列表彈出列表索引的兩種方式

    使用利用事件冒泡委托給列表的父節(jié)點去處理的方式第二種方式就是使用閉包了,感興趣的你可以參考下本文,或許對你學(xué)習(xí)js有所幫助
    2013-03-03

最新評論