成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

【進(jìn)階 6-3 期】深入淺出節(jié)流函數(shù) throttle

baishancloud / 2878人閱讀

摘要:引言上一節(jié)我們?cè)敿?xì)聊了聊高階函數(shù)之柯里化,通過(guò)介紹其定義和三種柯里化應(yīng)用,并在最后實(shí)現(xiàn)了一個(gè)通用的函數(shù)。第二種方案來(lái)實(shí)現(xiàn)也存在一個(gè)問(wèn)題,因?yàn)槎〞r(shí)器是延遲執(zhí)行的,所以事件停止觸發(fā)時(shí)必然會(huì)響應(yīng)回調(diào),所以時(shí)無(wú)法生效。

引言

上一節(jié)我們?cè)敿?xì)聊了聊高階函數(shù)之柯里化,通過(guò)介紹其定義和三種柯里化應(yīng)用,并在最后實(shí)現(xiàn)了一個(gè)通用的 currying 函數(shù)。這一小節(jié)會(huì)繼續(xù)之前的篇幅聊聊函數(shù)節(jié)流 throttle,給出這種高階函數(shù)的定義、實(shí)現(xiàn)原理以及在 underscore 中的實(shí)現(xiàn),歡迎大家拍磚。

有什么想法或者意見(jiàn)都可以在評(píng)論區(qū)留言,下圖是本文的思維導(dǎo)圖,高清思維導(dǎo)圖和更多文章請(qǐng)看我的 Github。

定義及解讀

函數(shù)節(jié)流指的是某個(gè)函數(shù)在一定時(shí)間間隔內(nèi)(例如 3 秒)只執(zhí)行一次,在這 3 秒內(nèi) 無(wú)視后來(lái)產(chǎn)生的函數(shù)調(diào)用請(qǐng)求,也不會(huì)延長(zhǎng)時(shí)間間隔。3 秒間隔結(jié)束后第一次遇到新的函數(shù)調(diào)用會(huì)觸發(fā)執(zhí)行,然后在這新的 3 秒內(nèi)依舊無(wú)視后來(lái)產(chǎn)生的函數(shù)調(diào)用請(qǐng)求,以此類(lèi)推。

舉一個(gè)小例子,不知道大家小時(shí)候有沒(méi)有養(yǎng)過(guò)小金魚(yú)啥的,養(yǎng)金魚(yú)肯定少不了接水,剛開(kāi)始接水時(shí)管道中水流很大,水到半滿(mǎn)時(shí)開(kāi)始擰緊水龍頭,減少水流的速度變成 3 秒一滴,通過(guò)滴水給小金魚(yú)增加氧氣。

此時(shí)「管道中的水」就是我們頻繁操作事件而不斷涌入的回調(diào)任務(wù),它需要接受「水龍頭」安排;「水龍頭」就是節(jié)流閥,控制水的流速,過(guò)濾無(wú)效的回調(diào)任務(wù);「滴水」就是每隔一段時(shí)間執(zhí)行一次函數(shù),「3 秒」就是間隔時(shí)間,它是「水龍頭」決定「滴水」的依據(jù)。

如果你還無(wú)法理解,看下面這張圖就清晰多了,另外點(diǎn)擊 這個(gè)頁(yè)面 查看節(jié)流和防抖的可視化比較。其中 Regular 是不做任何處理的情況,throttle 是函數(shù)節(jié)流之后的結(jié)果,debounce 是函數(shù)防抖之后的結(jié)果(下一小節(jié)介紹)。

原理及實(shí)現(xiàn)

函數(shù)節(jié)流非常適用于函數(shù)被頻繁調(diào)用的場(chǎng)景,例如:window.onresize() 事件、mousemove 事件、上傳進(jìn)度等情況。使用 throttle API 很簡(jiǎn)單,那應(yīng)該如何實(shí)現(xiàn) throttle 這個(gè)函數(shù)呢?

實(shí)現(xiàn)方案有以下兩種

第一種是用時(shí)間戳來(lái)判斷是否已到執(zhí)行時(shí)間,記錄上次執(zhí)行的時(shí)間戳,然后每次觸發(fā)事件執(zhí)行回調(diào),回調(diào)中判斷當(dāng)前時(shí)間戳距離上次執(zhí)行時(shí)間戳的間隔是否已經(jīng)達(dá)到時(shí)間差(Xms) ,如果是則執(zhí)行,并更新上次執(zhí)行的時(shí)間戳,如此循環(huán)。

第二種方法是使用定時(shí)器,比如當(dāng) scroll 事件剛觸發(fā)時(shí),打印一個(gè) hello world,然后設(shè)置個(gè) 1000ms 的定時(shí)器,此后每次觸發(fā) scroll 事件觸發(fā)回調(diào),如果已經(jīng)存在定時(shí)器,則回調(diào)不執(zhí)行方法,直到定時(shí)器觸發(fā),handler 被清除,然后重新設(shè)置定時(shí)器。

這里我們采用第一種方案來(lái)實(shí)現(xiàn),通過(guò)閉包保存一個(gè) previous 變量,每次觸發(fā) throttle 函數(shù)時(shí)判斷當(dāng)前時(shí)間和 previous 的時(shí)間差,如果這段時(shí)間差小于等待時(shí)間,那就忽略本次事件觸發(fā)。如果大于等待時(shí)間就把 previous 設(shè)置為當(dāng)前時(shí)間并執(zhí)行函數(shù) fn。

我們來(lái)一步步實(shí)現(xiàn),首先實(shí)現(xiàn)用閉包保存 previous 變量。

const throttle = (fn, wait) => {
    // 上一次執(zhí)行該函數(shù)的時(shí)間
  let previous = 0
  return function(...args) {
    console.log(previous)
    ...
  }
}

執(zhí)行 throttle 函數(shù)后會(huì)返回一個(gè)新的 function,我們命名為 betterFn。

const betterFn = function(...args) {
  console.log(previous)
    ...
}

betterFn 函數(shù)中可以獲取到 previous 變量值也可以修改,在回調(diào)監(jiān)聽(tīng)或事件觸發(fā)時(shí)就會(huì)執(zhí)行 betterFn,即 betterFn(),所以在這個(gè)新函數(shù)內(nèi)判斷當(dāng)前時(shí)間和 previous 的時(shí)間差即可。

const betterFn = function(...args) {
  let now = +new Date();
  if (now - previous > wait) {
    previous = now
    // 執(zhí)行 fn 函數(shù)
    fn.apply(this, args)
  }
}

結(jié)合上面兩段代碼就實(shí)現(xiàn)了節(jié)流函數(shù),所以完整的實(shí)現(xiàn)如下。

// fn 是需要執(zhí)行的函數(shù)
// wait 是時(shí)間間隔
const throttle = (fn, wait = 50) => {
  // 上一次執(zhí)行 fn 的時(shí)間
  let previous = 0
  // 將 throttle 處理結(jié)果當(dāng)作函數(shù)返回
  return function(...args) {
    // 獲取當(dāng)前時(shí)間,轉(zhuǎn)換成時(shí)間戳,單位毫秒
    let now = +new Date()
    // 將當(dāng)前時(shí)間和上一次執(zhí)行函數(shù)的時(shí)間進(jìn)行對(duì)比
    // 大于等待時(shí)間就把 previous 設(shè)置為當(dāng)前時(shí)間并執(zhí)行函數(shù) fn
    if (now - previous > wait) {
      previous = now
      fn.apply(this, args)
    }
  }
}

// DEMO
// 執(zhí)行 throttle 函數(shù)返回新函數(shù)
const betterFn = throttle(() => console.log("fn 函數(shù)執(zhí)行了"), 1000)
// 每 10 秒執(zhí)行一次 betterFn 函數(shù),但是只有時(shí)間差大于 1000 時(shí)才會(huì)執(zhí)行 fn
setInterval(betterFn, 10)
underscore 源碼解讀

上述代碼實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的節(jié)流函數(shù),不過(guò) underscore 實(shí)現(xiàn)了更高級(jí)的功能,即新增了兩個(gè)功能

配置是否需要響應(yīng)事件剛開(kāi)始的那次回調(diào)( leading 參數(shù),false 時(shí)忽略)

配置是否需要響應(yīng)事件結(jié)束后的那次回調(diào)( trailing 參數(shù),false 時(shí)忽略)

配置 { leading: false } 時(shí),事件剛開(kāi)始的那次回調(diào)不執(zhí)行;配置 { trailing: false } 時(shí),事件結(jié)束后的那次回調(diào)不執(zhí)行,不過(guò)需要注意的是,這兩者不能同時(shí)配置。

所以在 underscore 中的節(jié)流函數(shù)有 3 種調(diào)用方式,默認(rèn)的(有頭有尾),設(shè)置 { leading: false } 的,以及設(shè)置 { trailing: false } 的。上面說(shuō)過(guò)實(shí)現(xiàn) throttle 的方案有 2 種,一種是通過(guò)時(shí)間戳判斷,另一種是通過(guò)定時(shí)器創(chuàng)建和銷(xiāo)毀來(lái)控制。

第一種方案實(shí)現(xiàn)這 3 種調(diào)用方式存在一個(gè)問(wèn)題,即事件停止觸發(fā)時(shí)無(wú)法響應(yīng)回調(diào),所以 { trailing: true } 時(shí)無(wú)法生效。

第二種方案來(lái)實(shí)現(xiàn)也存在一個(gè)問(wèn)題,因?yàn)槎〞r(shí)器是延遲執(zhí)行的,所以事件停止觸發(fā)時(shí)必然會(huì)響應(yīng)回調(diào),所以 { trailing: false } 時(shí)無(wú)法生效。

underscore 采用的方案是兩種方案搭配使用來(lái)實(shí)現(xiàn)這個(gè)功能。

const throttle = function(func, wait, options) {
  var timeout, context, args, result;
  
  // 上一次執(zhí)行回調(diào)的時(shí)間戳
  var previous = 0;
  
  // 無(wú)傳入?yún)?shù)時(shí),初始化 options 為空對(duì)象
  if (!options) options = {};

  var later = function() {
    // 當(dāng)設(shè)置 { leading: false } 時(shí)
    // 每次觸發(fā)回調(diào)函數(shù)后設(shè)置 previous 為 0
    // 不然為當(dāng)前時(shí)間
    previous = options.leading === false ? 0 : _.now();
    
    // 防止內(nèi)存泄漏,置為 null 便于后面根據(jù) !timeout 設(shè)置新的 timeout
    timeout = null;
    
    // 執(zhí)行函數(shù)
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  // 每次觸發(fā)事件回調(diào)都執(zhí)行這個(gè)函數(shù)
  // 函數(shù)內(nèi)判斷是否執(zhí)行 func
  // func 才是我們業(yè)務(wù)層代碼想要執(zhí)行的函數(shù)
  var throttled = function() {
    
    // 記錄當(dāng)前時(shí)間
    var now = _.now();
    
    // 第一次執(zhí)行時(shí)(此時(shí) previous 為 0,之后為上一次時(shí)間戳)
    // 并且設(shè)置了 { leading: false }(表示第一次回調(diào)不執(zhí)行)
    // 此時(shí)設(shè)置 previous 為當(dāng)前值,表示剛執(zhí)行過(guò),本次就不執(zhí)行了
    if (!previous && options.leading === false) previous = now;
    
    // 距離下次觸發(fā) func 還需要等待的時(shí)間
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    
    // 要么是到了間隔時(shí)間了,隨即觸發(fā)方法(remaining <= 0)
    // 要么是沒(méi)有傳入 {leading: false},且第一次觸發(fā)回調(diào),即立即觸發(fā)
    // 此時(shí) previous 為 0,wait - (now - previous) 也滿(mǎn)足 <= 0
    // 之后便會(huì)把 previous 值迅速置為 now
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        
        // clearTimeout(timeout) 并不會(huì)把 timeout 設(shè)為 null
        // 手動(dòng)設(shè)置,便于后續(xù)判斷
        timeout = null;
      }
      
      // 設(shè)置 previous 為當(dāng)前時(shí)間
      previous = now;
      
      // 執(zhí)行 func 函數(shù)
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      // 最后一次需要觸發(fā)的情況
      // 如果已經(jīng)存在一個(gè)定時(shí)器,則不會(huì)進(jìn)入該 if 分支
      // 如果 {trailing: false},即最后一次不需要觸發(fā)了,也不會(huì)進(jìn)入這個(gè)分支
      // 間隔 remaining milliseconds 后觸發(fā) later 方法
      timeout = setTimeout(later, remaining);
    }
    return result;
  };

  // 手動(dòng)取消
  throttled.cancel = function() {
    clearTimeout(timeout);
    previous = 0;
    timeout = context = args = null;
  };

  // 執(zhí)行 _.throttle 返回 throttled 函數(shù)
  return throttled;
};
小結(jié)

函數(shù)節(jié)流指的是某個(gè)函數(shù)在一定時(shí)間間隔內(nèi)(例如 3 秒)只執(zhí)行一次,在這 3 秒內(nèi) 無(wú)視后來(lái)產(chǎn)生的函數(shù)調(diào)用請(qǐng)求

節(jié)流可以理解為養(yǎng)金魚(yú)時(shí)擰緊水龍頭放水,3 秒一滴

「管道中的水」就是我們頻繁操作事件而不斷涌入的回調(diào)任務(wù),它需要接受「水龍頭」安排

「水龍頭」就是節(jié)流閥,控制水的流速,過(guò)濾無(wú)效的回調(diào)任務(wù)

「滴水」就是每隔一段時(shí)間執(zhí)行一次函數(shù)

「3 秒」就是間隔時(shí)間,它是「水龍頭」決定「滴水」的依據(jù)

節(jié)流實(shí)現(xiàn)方案有 2 種

第一種是用時(shí)間戳來(lái)判斷是否已到執(zhí)行時(shí)間,記錄上次執(zhí)行的時(shí)間戳,然后每次觸發(fā)事件執(zhí)行回調(diào),回調(diào)中判斷當(dāng)前時(shí)間戳距離上次執(zhí)行時(shí)間戳的間隔是否已經(jīng)達(dá)到時(shí)間差(Xms) ,如果是則執(zhí)行,并更新上次執(zhí)行的時(shí)間戳,如此循環(huán)。

第二種方法是使用定時(shí)器,比如當(dāng) scroll 事件剛觸發(fā)時(shí),打印一個(gè) hello world,然后設(shè)置個(gè) 1000ms 的定時(shí)器,此后每次觸發(fā) scroll 事件觸發(fā)回調(diào),如果已經(jīng)存在定時(shí)器,則回調(diào)不執(zhí)行方法,直到定時(shí)器觸發(fā),handler 被清除,然后重新設(shè)置定時(shí)器。

參考
underscore.js

前端性能優(yōu)化原理與實(shí)踐

underscore 函數(shù)節(jié)流的實(shí)現(xiàn)

文章穿梭機(jī)

【進(jìn)階 6-2 期】深入高階函數(shù)應(yīng)用之柯里化

【進(jìn)階 6-1 期】JavaScript 高階函數(shù)淺析

【進(jìn)階 5-3 期】深入探究 Function & Object 雞蛋問(wèn)題

【進(jìn)階 5-2 期】圖解原型鏈及其繼承優(yōu)缺點(diǎn)

【進(jìn)階 5-1 期】重新認(rèn)識(shí)構(gòu)造函數(shù)、原型和原型鏈

?? 看完三件事

如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

點(diǎn)贊,讓更多的人也能看到這篇內(nèi)容(收藏不點(diǎn)贊,都是耍流氓 -_-

關(guān)注我的 GitHub,讓我們成為長(zhǎng)期關(guān)系

關(guān)注公眾號(hào)「高級(jí)前端進(jìn)階」,每周重點(diǎn)攻克一個(gè)前端面試重難點(diǎn),公眾號(hào)后臺(tái)回復(fù)「資料」 送你精選前端優(yōu)質(zhì)資料。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109934.html

相關(guān)文章

  • JS進(jìn)階篇1---函數(shù)節(jié)流throttle

    摘要:主要實(shí)現(xiàn)思路就是通過(guò)定時(shí)器,通過(guò)設(shè)置延時(shí)時(shí)間,在第一次調(diào)用時(shí),創(chuàng)建定時(shí)器,寫(xiě)入需要執(zhí)行的函數(shù)。如果這時(shí)前一個(gè)定時(shí)器暫未執(zhí)行,則將其替換為新的定時(shí)器。 JS中的函數(shù)節(jié)流 一、什么是函數(shù)節(jié)流(throttle) 概念:限制一個(gè)函數(shù)在一定時(shí)間內(nèi)只能執(zhí)行一次。 舉個(gè)栗子,坐火車(chē)或地鐵,過(guò)安檢的時(shí)候,在一定時(shí)間(例如10秒)內(nèi),只允許一個(gè)乘客通過(guò)安檢入口,以配合安檢人員完成安檢工作。上例中,每1...

    zhou_you 評(píng)論0 收藏0
  • JS進(jìn)階篇--JS函數(shù)節(jié)流throttle

    摘要:函數(shù)節(jié)流的原理函數(shù)節(jié)流的原理挺簡(jiǎn)單的,估計(jì)大家都想到了,那就是定時(shí)器。在高級(jí)程序設(shè)計(jì)一書(shū)有介紹函數(shù)節(jié)流,里面封裝了這樣一個(gè)函數(shù)節(jié)流函數(shù),它把定時(shí)器存為函數(shù)的一個(gè)屬性個(gè)人的世界觀不喜歡這種寫(xiě)法。 什么是函數(shù)節(jié)流? 介紹前,先說(shuō)下背景。在前端開(kāi)發(fā)中,有時(shí)會(huì)為頁(yè)面綁定resize事件,或者為一個(gè)頁(yè)面元素綁定拖拽事件(其核心就是綁定mousemove),這種事件有一個(gè)特點(diǎn),就是用戶(hù)不必特地?fù)v亂...

    cpupro 評(píng)論0 收藏0
  • 進(jìn)階2-2】JavaScript深入之從作用域鏈理解閉包

    摘要:使用上一篇文章的例子來(lái)說(shuō)明下自由變量進(jìn)階期深入淺出圖解作用域鏈和閉包訪問(wèn)外部的今天是今天是其中既不是參數(shù),也不是局部變量,所以是自由變量。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第7天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)...

    simpleapples 評(píng)論0 收藏0
  • 進(jìn)階2-1深入淺出圖解作用域鏈和閉包

    摘要:本期推薦文章從作用域鏈談閉包,由于微信不能訪問(wèn)外鏈,點(diǎn)擊閱讀原文就可以啦。推薦理由這是一篇譯文,深入淺出圖解作用域鏈,一步步深入介紹閉包。作用域鏈的頂端是全局對(duì)象,在全局環(huán)境中定義的變量就會(huì)綁定到全局對(duì)象中。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第6天。 本...

    levius 評(píng)論0 收藏0
  • 進(jìn)階1-2】JavaScript深入之執(zhí)行上下文棧和變量對(duì)象

    摘要:本計(jì)劃一共期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)劃,點(diǎn)擊查看前端進(jìn)階的破冰之旅本期推薦文章深入之執(zhí)行上下文棧和深入之變量對(duì)象,由于微信不能訪問(wèn)外鏈,點(diǎn)擊閱讀原文就可以啦。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,今天是第二天。 本計(jì)劃一共28期,每期...

    Richard_Gao 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<