亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

最近在看紅樓夢(mèng),看的詩(shī)詞多了,時(shí)不時(shí)的也想來(lái)一句...

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

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

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

防抖

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

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

基本實(shí)現(xiàn)

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

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

這樣我們就寫出了一個(gè)最基本版的防抖函數(shù)??梢钥吹接|發(fā)次數(shù)已經(jīng)大大降低。

this & arguments

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

我們可以在執(zhí)行定時(shí)器之前進(jìn)行重置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ù)進(jìn)行傳參呢,如果我們想在func函數(shù)中傳遞event對(duì)象,目前的實(shí)現(xiàn)顯然是無(wú)法正確進(jìn)行獲取參數(shù)的,再來(lái)修改一下:

 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);
   }
 }

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

立即執(zhí)行

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

那么這個(gè)功能怎么做呢,其實(shí)可以這樣想,我們可以傳入一個(gè)參數(shù)immediate,代表是否想要立即執(zhí)行,如果傳遞了immediate,則立即執(zhí)行一次函數(shù),然后設(shè)置一個(gè)定時(shí)器,時(shí)間截止后將定時(shí)器設(shè)置為null,下次進(jìn)入函數(shù)時(shí)先判斷定時(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,則重新進(jìn)行設(shè)置
     if(timeout) clearTimeout(timeout)
     // 判斷是否為立即執(zhí)行
     if(immediate) {
       let runNow = !timeout
       // 設(shè)置定時(shí)器,指定時(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 {
       // 如果沒(méi)有設(shè)置立即執(zhí)行,則設(shè)置定時(shí)器
       timeout = setTimeout(function() {
         func.apply(context, args)
       }, wait)
     }
     return res
   }
 
   return debounced
 }

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

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

非立即執(zhí)行

立即執(zhí)行

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

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

第二次執(zhí)行:

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

再來(lái)看一下immediatefalse的情況:

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

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

取消

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

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

只需要將定時(shí)器清除,設(shè)置timeoutnull即可,因?yàn)槿绻?code>immediate 為 true會(huì)直接執(zhí)行一次函數(shù),然后重新設(shè)置定時(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ā)事件,每隔一段時(shí)間,只執(zhí)行一次事件。也就是只按照設(shè)置的時(shí)間作為時(shí)間段,到達(dá)指定的時(shí)間后觸發(fā)函數(shù)就會(huì)執(zhí)行。沒(méi)有到達(dá)指定的時(shí)間,無(wú)論如何觸發(fā)函數(shù)都不會(huì)執(zhí)行。

也就是沒(méi)到點(diǎn),無(wú)論你怎么撩,我都巋然不動(dòng) 

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

時(shí)間戳

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

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

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

+new Date() 將會(huì)調(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í)器

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

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

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

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

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

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

兩者結(jié)合

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

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

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

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

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

還是依舊縷一下思路:

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

第二次觸發(fā):

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

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

其實(shí)核心在于remaining這個(gè)變量的運(yùn)算。

控制執(zhí)行時(shí)機(jī)

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

可以傳遞第三個(gè)參數(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進(jìn)行同步
      // now 與 previous 相減不小于0,則不會(huì)執(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;
}

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

那就是 leading:falsetrailing: false 不能同時(shí)設(shè)置。因?yàn)槿绻瑫r(shí)設(shè)置,那么就是既不開(kāi)始觸發(fā)也不結(jié)束時(shí)觸發(fā),那么函數(shù)將不會(huì)正常執(zhí)行。

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

取消

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

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

完整實(shí)現(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é)流的實(shí)現(xiàn)方式。

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

相關(guān)文章

  • javascript利用鍵盤控制小方塊的移動(dòng)

    javascript利用鍵盤控制小方塊的移動(dòng)

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

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

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

    微信小程序block的使用教程

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最新評(píng)論