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

資訊專欄INFORMATION COLUMN

【源碼分析】給你幾個鬧鐘,或許用 10 分鐘就能寫出 lodash 中的 debounce &

余學(xué)文 / 2872人閱讀

摘要:最簡單的案例以最簡單的情景為例在某一時刻點(diǎn)只調(diào)用一次函數(shù),那么將在時間后才會真正觸發(fā)函數(shù)。后續(xù)我們會逐漸增加黑色鬧鐘出現(xiàn)的復(fù)雜度,不斷去分析紅色鬧鐘的位置。

相比網(wǎng)上教程中的 debounce 函數(shù),lodash 中的 debounce 功能更為強(qiáng)大,相應(yīng)的理解起來更為復(fù)雜;

解讀源碼一般都是直接拿官方源碼來解讀,不過這次我們采用另外的方式:從最簡單的場景開始寫代碼,然后慢慢往源碼上來靠攏,循序漸進(jìn)來實現(xiàn) lodash 中的 debounce 函數(shù),從而更深刻理解官方 debounce 源碼的用意。

為了減少純代碼帶來的晦澀感,本文以圖例來輔助講解,一方面這樣能減少源碼閱讀帶來的枯燥感,同時也讓后續(xù)回憶源碼內(nèi)容更加的具體形象。(記住圖的內(nèi)容,后續(xù)再寫出源碼也變得簡單些)

在本文的末尾還會附上簡易的 debounce & throttle 的實現(xiàn)的代碼片段,方便平時快速用在簡單場景中,免去引用 lodash 庫。

本文屬于源碼解讀類型的文章,對 debounce 還不熟悉的讀者建議先通過參考文章(在文末)了解該函數(shù)的概念和用法。
1、用圖例解析 debounce 源碼
附源碼 debounce: https://github.com/boycgit/ts...

首先搬出 debounce(防抖)函數(shù)的概念:函數(shù)在 wait 秒內(nèi)只執(zhí)行一次,若這 wait 秒內(nèi),函數(shù)高頻觸發(fā),則會重新計算時間。

看似簡單一句話,內(nèi)含乾坤。為方便行文敘述,約定如下術(shù)語:

假定我們要對 func 函數(shù)進(jìn)行 debounce 處理,經(jīng) debounced 后的返回值我們稱之為 debounced func

wait 表示傳入防抖函數(shù)的時間

time 表示當(dāng)前時間戳

lastCallTime 表示上一次調(diào)用 debounced func 函數(shù)的時間

lastInvokeTime 表示上一次 func 函數(shù)執(zhí)行的時間

result 是每次調(diào)用 debounced func 函數(shù)的返回值

time 表示當(dāng)前時間

本文將搭配圖例 + 程序代碼的方式,將上述概念具象到圖中。

2、最簡單的案例

以最簡單的情景為例:在某一時刻點(diǎn)只調(diào)用一次 debounced func 函數(shù),那么將在 wait 時間后才會真正觸發(fā) func 函數(shù)。

將這個情景形成一幅圖例,最終繪制出的圖如下所示:

下面我們詳細(xì)講解這幅圖的產(chǎn)生過程,其實不難,基本上看一遍就懂。

首先繪制在圖中放置一個黑色鬧鐘表示用戶調(diào)用 debounced func 函數(shù):(同時用 lastCallTime 標(biāo)示出最近一次調(diào)用 debounced func 的時間)

同時在距離該黑色鬧鐘 wait 處放置一個藍(lán)色鬧鐘,表示setTimout(..., wait),該藍(lán)色鬧鐘表示未來當(dāng)代碼運(yùn)行到該時間點(diǎn)時,需要做一些判斷:

為了標(biāo)示出表示程序當(dāng)前運(yùn)行的進(jìn)度(當(dāng)前時間戳),我們用橙紅色滑塊來表示:

當(dāng)紅色滑塊到達(dá)該藍(lán)色鬧鐘處的時候,藍(lán)色鬧鐘會進(jìn)行判斷:因為當(dāng)前滑塊距離最近的黑色鬧鐘的時間差為 wait

故而做出判斷(依據(jù) debounce 函數(shù)的功能定義):需要觸發(fā)一次 func 函數(shù),我們用紅色鬧鐘來表示 func 函數(shù)的調(diào)用,所以就放置一個紅色鬧鐘

很顯然藍(lán)色和紅色鬧鐘重疊起來的。

同時我們給紅色鬧鐘標(biāo)上 lastInvokeTime,記錄最近一次調(diào)用 func 的時間:

注意 lastInvokeTimelastCallTime 的區(qū)別,兩者含義是不一樣的

這樣我們就完成了最簡單場景下 debounce 圖例的繪制,簡單易懂。

后續(xù)我們會逐漸增加黑色鬧鐘出現(xiàn)的復(fù)雜度,不斷去分析紅色鬧鐘的位置。這樣就能將理解 debounce 源碼的問題轉(zhuǎn)換成“根據(jù)圖上黑色鬧鐘的位置,請畫出紅色鬧鐘位置”的問題,而分析紅色鬧鐘位置的過程中也就是理解 debounce 源碼的過程;

用圖例方式輔助理解源碼的方式可以減少源碼閱讀帶來的枯燥感,同時后續(xù)回憶源碼內(nèi)容起來也更加具體形象。

為避免后續(xù)寫文章到處解釋圖中元素的概念含義,這里不妨先羅列出來,如果閱讀過程中忘記到這里回憶一下也會方便許多:

橫線代表時間軸,橙紅色滑塊代表當(dāng)前時間 time

每個黑色箭頭表示 debounced func 函數(shù)的調(diào)用

黑色鬧鐘表示調(diào)用 debounced func 函數(shù)時的時間,最后一次黑色鬧鐘上標(biāo)上 lastCallTime,表示最近一次調(diào)用的時間戳;

紅色鬧鐘表示調(diào)用 func 函數(shù)的時間,最后一次紅色鬧鐘上標(biāo)上 lastInvokeTime,表示最近一次調(diào)用的時間戳;

此外還有一個藍(lán)色鬧鐘,表示 setTimeout 時間戳(用來規(guī)劃 func 函數(shù)執(zhí)行的時間),每次時間軸上的橙紅色滑塊到這個時間點(diǎn)就要做判斷:是執(zhí)行 func 或者推遲藍(lán)色鬧鐘位置

有關(guān)藍(lán)色鬧鐘,這里有兩個注意點(diǎn):

時間軸上最多同時只有一個藍(lán)色鬧鐘;

只有在第一次調(diào)用 debounced func 函數(shù)時才會在 wait 時間后放置藍(lán)色鬧鐘,后續(xù)鬧鐘的出現(xiàn)位置就由藍(lán)色鬧鐘自己決策(下文會舉例說明)

3、有 N 多個黑色鬧鐘的場景

現(xiàn)在我們來一個稍微復(fù)雜的場景:

假如在 wait 時間內(nèi)(記住這個前提條件)調(diào)用 n 次 debounced func 函數(shù),如下所示:

第一次調(diào)用 debounced func 函數(shù)會在 wait 時間后放置藍(lán)色鬧鐘(只有第一次調(diào)用會放置藍(lán)色鬧鐘,后續(xù)鬧鐘的位置由藍(lán)色鬧鐘自己決策):

以上就是描述,那么問題來了:請問紅色鬧鐘應(yīng)該出現(xiàn)在時間軸哪個位置?

3.1、分析紅色鬧鐘出現(xiàn)的位置

我們只關(guān)注最后一個黑色鬧鐘,并假設(shè)藍(lán)色鬧鐘距離該黑色鬧鐘時間間隔為 x

那么第一個黑色鬧鐘和最后一個黑色鬧鐘的時間間隔是 wait - x

接下來我們關(guān)注橙紅色滑塊(即當(dāng)前時間time)到達(dá)藍(lán)色鬧鐘的時,藍(lán)色鬧鐘開始做決策:計算可知 x < wait,此時藍(lán)色鬧鐘決定不放置紅色鬧鐘(即不觸發(fā) func),而是將藍(lán)色鬧鐘往后挪了挪,挪動距離為 wait - x,調(diào)整完之后的藍(lán)色鬧鐘位置如下:

之所以挪 wait - x 的距離,是因為挪完后的藍(lán)色鬧鐘距離最后一個黑色鬧鐘恰好為 wait 間隔(從而保證 debounce 函數(shù)至少間隔 wait 時間 才觸發(fā)的條件):

從挪移之后開始,到下一次橙色鬧鐘再次遇到藍(lán)色鬧鐘這段期間,我們暫且稱之為 ”藍(lán)色決策間隔期“(請忍耐這抽象的名稱,畢竟我想了好久),藍(lán)色鬧鐘基于此間隔期的內(nèi)容來進(jìn)行決策,只有兩種決策:

如果在”藍(lán)色決策間隔期“內(nèi)沒有黑鬧鐘出現(xiàn),那么紅色滑塊達(dá)到藍(lán)色鬧鐘的時候,藍(lán)色鬧鐘計算獲知當(dāng)前藍(lán)色鬧鐘距離上一個黑色鬧鐘的時間間隔不少于 waittime - lastCallTime >= wait),那就會放置紅色鬧鐘(即調(diào)用 func),目標(biāo)達(dá)成;

如果在”藍(lán)色決策間隔期“內(nèi)仍舊有黑鬧鐘出現(xiàn),那么當(dāng)橙紅色滑塊到達(dá)藍(lán)色鬧鐘時,藍(lán)色鬧鐘又會重新計算與該間隔期內(nèi)最后一只黑色鬧鐘的距離 y,隨后 又會往后挪動位置 wait-y,再一次保證藍(lán)色鬧鐘距離最后一個黑色鬧鐘恰好為 wait 間隔 —— 沒錯,又形成了新的 ”藍(lán)色決策間隔期“;那接下去的分析就又回到了 這里兩點(diǎn)(即遞歸決策),直到能放置到紅鬧鐘為止。

從上我們可以看到,藍(lán)色鬧鐘一直保持 ”紳士“ 風(fēng)范,隨著黑色鬧鐘的逼近,藍(lán)色鬧鐘一直保持”克制“態(tài)度,不斷調(diào)整自己的位置,讓調(diào)整后的位置總是和最后一個黑色鬧鐘保持 wait 的距離。

3.2、用代碼描述圖例過程

我們用代碼將上述的過程描述出來,就是下面這個樣子:

function debounce(func, wait, options) {
  var lastArgs, lastThis, result, timerId, lastCallTime, lastInvokeTime = 0, trailing = true;
  
  wait = toNumber(wait) || 0;  

  // 紅色滑塊達(dá)到藍(lán)色鬧鐘時,藍(lán)色鬧鐘根據(jù)條件作出決策
  function timerExpired() {
    var time = now();

    // 決策 1: 滿足放置紅色鬧鐘的條件,則放置紅鬧鐘
    if (shouldInvoke(time)) {
      return trailingEdge(time);
    }
    // 否則,決策 2:將藍(lán)色鬧鐘再往后挪 `wait-x` 位置,形成  ”藍(lán)色決策間隔期“
    timerId = setTimeout(timerExpired, remainingWait(time));
  }

  // === 以下是具體決策中的函數(shù)實現(xiàn) ==== 
   // 做出 ”應(yīng)當(dāng)放置紅色鬧鐘“ 的決策的條件:藍(lán)色鬧鐘和最后一個黑色鬧鐘的間隔不小于 wait 間隔
  function shouldInvoke(time) {
    var timeSinceLastCall = time - lastCallTime;
    return (
      timeSinceLastCall >= wait
    );
  }

  // 具體函數(shù):放置紅色鬧鐘
  function trailingEdge(time) {
    timerId = undefined;
    
    if (trailing && lastArgs) {
      return invokeFunc(time);
    }
    lastArgs = lastThis = undefined;
    return result;
  }
  // 具體函數(shù) - 子函數(shù):在時間軸上放置紅鬧鐘
  function invokeFunc(time) {
    var args = lastArgs,
      thisArg = lastThis;

    lastArgs = lastThis = undefined;
    lastInvokeTime = time;
    result = func.apply(thisArg, args);
    return result;
  }  
  
  // 具體函數(shù):計算讓藍(lán)色鬧鐘往后挪 wait-x 位置
  function remainingWait(time) {
    var timeSinceLastCall = time - lastCallTime,
      timeWaiting = wait - timeSinceLastCall;

    return timeWaiting ;
  }  
  // ==============


 // 主流程:讓紅色滑塊在時間軸上前進(jìn)(即 debounced func 函數(shù)的執(zhí)行)
 function debounced() {
    var time = now();
    lastArgs = arguments;
    lastThis = this;
    lastCallTime = time;

    if (timerId === undefined) {
      timerId = setTimeout(timerExpired, wait);
    }
    return result;
  }
  return debounced;
}

這部分代碼還請不要略過,因為代碼是從debounce源碼中整理過來,除了函數(shù)順序略有調(diào)整外,源碼風(fēng)格保持原有的,相當(dāng)于直接閱讀源碼。每個函數(shù)都有注釋,對比著圖例閱讀下來相信讀完會有收獲的。

上述這份代碼已經(jīng)包含了 debounce 源碼的核心骨架,接下來我們繼續(xù)擴(kuò)展場景,將源碼內(nèi)容豐滿起來。

4、豐富功能特性 4.1、支持 leading 特性

leading 功能簡單理解就是,在第一次(注意這個條件)放下黑色鬧鐘的時候:

立即放置紅鬧鐘,同時在

此后 wait 處放置方式藍(lán)色鬧鐘(注:第一次放下黑色鬧鐘的時候,按理說也會在 wait 處放下藍(lán)色鬧鐘,考慮既然 leading 也有這種操作,那么就不多此一舉。記?。赫麄€時間軸上最多只能同時有一個藍(lán)色鬧鐘

用圖說話:

第一次放置黑色鬧鐘的時候,會疊加上紅色鬧鐘(當(dāng)然這個紅色鬧鐘上會標(biāo)示 lastInvokeTime),另外在 wait 間隔后會有藍(lán)色鬧鐘。其他流程和之前案例分析一樣。

在代碼層面,我們給剛才的 debounce 函數(shù)添加 leading 功能(通過 options.leading 開啟)、新增一個 leadingEdge 方法后,再微調(diào)剛才的代碼:

function debounce(func, wait, options) {
  ...
  
  var leading = false; // 默認(rèn)不開啟
  leading = !!options.leading; // 通過 options.leading 開啟
  
  ...
  
  // 首先:新增執(zhí)行 leading 處的操作的函數(shù)
  function leadingEdge(time) {
    lastInvokeTime = time; // 設(shè)置 lastInvokeTime 時間標(biāo)簽
    timerId = setTimeout(timerExpired, wait); // 同時在此后 `wait` 處放置一個藍(lán)色鬧鐘
    return leading ? invokeFunc(time) : result; // 如果開啟,直接放置紅色鬧鐘;否則直接返回 result 數(shù)值
  }
  ...
  
  // 其次:給放置紅色鬧鐘新增一種條件
   function shouldInvoke(time) {
    ...
    return (
      lastCallTime === undefined || // 初次執(zhí)行時
      timeSinceLastCall >= wait // 或者前面分析的條件,兩次 `debounced func` 調(diào)用間隔大于 wait 
    );
  }
  
   // 注意:放置完紅色鬧鐘后,記得要清空 timerId,相當(dāng)于清空時間軸上藍(lán)色鬧鐘;
  function trailingEdge(time) {
    timerId = undefined;
    ... 
  }
  
  // 最后:leading 邊界調(diào)用
  function debounced(){
    ...
    var isInvoking = shouldInvoke(time); //  判斷是否可以放置紅色鬧鐘
    
    ...
    
    if (isInvoking) { // 如果可以放置紅色鬧鐘
      
      if (timerId === undefined) { // 且當(dāng)時間軸上沒有藍(lán)色鬧鐘
        // 執(zhí)行 leading 邊界處操作(放置紅色鬧鐘 或 直接返 result)
        return leadingEdge(lastCallTime);
      }
      
    }
    
    ...
    return result;
  }

  return debounced;
}
4.2、支持 maxWait 特性

要理解這個 maxWait 特性,我們先看一種特殊情況,在 {leading: false} 下, 時間軸上我們很密集地放置黑色鬧鐘:

按之前的所述規(guī)則,我們的藍(lán)色鬧鐘一直保持紳士態(tài)度,隨著黑色鬧鐘的逼近,藍(lán)色鬧鐘將不斷將調(diào)整自己的位置,讓自己調(diào)整后的位置總是和最后一個黑色鬧鐘保持 wait 的距離:

那么在這種情況下,如果黑色鬧鐘一直保持這種密集放置狀態(tài),理論上就紅色鬧鐘就沒有機(jī)會出現(xiàn)在時間軸上。

那在這種情況下能否實現(xiàn)一個功能,無論黑色鬧鐘多么密集,時間軸上最多隔 maxWait 時間就出現(xiàn)紅色鬧鐘,就像下圖那樣:

有了這個功能屬性后,藍(lán)色鬧鐘從此 ”變得堅強(qiáng)“,也有了 "底線",縱使黑色鬧鐘的不斷逼近,也會堅守 maxWait 底線,到點(diǎn)就放置紅色鬧鐘。

實現(xiàn)該特性的大致思路如下:

maxWait 是與 lastInvokeTime 共同協(xié)作

在藍(lán)色鬧鐘計算后退距離時,maxWait 發(fā)揮作用;在沒有 maxWait 的時候,是按上一次黑色鬧鐘進(jìn)行測距,保證調(diào)整后的藍(lán)色鬧鐘和黑色鬧鐘保持 wait 的距離;而在有了 maxWait 后,藍(lán)色鬧鐘調(diào)整距離還會考慮上一次紅色鬧鐘的位置,保持調(diào)整后鬧鐘的位置和紅色鬧鐘距離不能超過 maxWait,這就是底線了,到了一定程度,就算黑色鬧鐘在逼近,藍(lán)色鬧鐘也不會 ”退縮“:

從代碼層面上看, maxWait 具體是在 remainingWait 方法 和 shouldInvoke 中發(fā)揮作用的:

function debounce(func, wait, options) {
  ...
  
  var lastInvokeTime = 0; // 初始化
  var maxing = false; // 默認(rèn)沒有底線
  
  maxing = "maxWait" in options;
  maxWait = maxing
      ? nativeMax(toNumber(options.maxWait) || 0, wait)
      : maxWait; // 從 options.maxWait 中獲取底線數(shù)值
  
  ...
  // 首先,在在藍(lán)色鬧鐘決策后退多少距離時,maxWait 發(fā)揮了作用
  function remainingWait(time) {
    var timeSinceLastCall = time - lastCallTime,
      timeSinceLastInvoke = time - lastInvokeTime,
      timeWaiting = wait - timeSinceLastCall;

    // 在這里發(fā)揮作用,保持底線
    return maxing
      ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
      : timeWaiting;
  }
  
  ...

  
  // 其次:針對 `maxWait`,給放置紅色鬧鐘新增一種可能條件
   function shouldInvoke(time) {
    ...
    var timeSinceLastInvoke = time - lastInvokeTime; // 獲取距離上一次紅色鬧鐘的時間間隔
    return (
      lastCallTime === undefined || // 初次執(zhí)行時
      timeSinceLastCall >= wait ||  // 或者前面分析的條件,兩次 `debounced func` 調(diào)用間隔大于 wait 
      (maxing && timeSinceLastInvoke >= maxWait) // 兩次紅色鬧鐘間隔超過 maxWait
    );
  }
  
  
  // 最后:leading 邊界調(diào)用
  function debounced(){
    ...
    var isInvoking = shouldInvoke(time); //  判斷是否可以放置紅色鬧鐘的條件
    
    ...
    
    if (isInvoking) { // 如果可以放置紅色鬧鐘
      
      ...
      // 邊界情況的處理,保證在緊 loop 中能正常保持觸發(fā)
      if (maxing) {
        timerId = setTimeout(timerExpired, wait);
        return invokeFunc(lastCallTime);
      }
      
    }
    
    ...
    return result;
  }

  return debounced;
}

因此,maxWait 能夠讓紅色鬧鐘保證在 maxWait 間隔內(nèi)至少出現(xiàn) 1 次;

4.3、支持 cancel / flush 方法

這兩個函數(shù)是為了能隨時控制 debounce 的緩存狀態(tài);

其中 cancel 方法源碼如下:

 //  取消防抖
  function cancel() {
    if (timerId !== undefined) {
      clearTimeout(timerId);
    }
    lastInvokeTime = 0;
    lastArgs = lastCallTime = lastThis = timerId = undefined;
  }

調(diào)用該方法,相當(dāng)于直接在時間軸上去除藍(lán)色鬧鐘,這樣紅色方塊(時間)就永遠(yuǎn)遇見不了藍(lán)色鬧鐘,那樣也就不會有放置紅色鬧鐘的可能了。

其中 flush 方法源碼如下:

function flush() {
  return timerId === undefined ? result : trailingEdge(now());
}

非常直觀,調(diào)用該方法相當(dāng)于直接在時間軸上放置紅色鬧鐘。

至此,我們已經(jīng)完整實現(xiàn)了 lodash 的 debounce 函數(shù),也就相當(dāng)于閱讀了一遍其源碼。

5、實現(xiàn) throttle 函數(shù)

在完成上面 debounce 功能和特性后(尤其是 maxWait 特性),就能借助 debounce 實現(xiàn) throttle 函數(shù)了。

看 throttle 源碼 就能明白:

function throttle(func, wait, options) {
  var leading = true,
      trailing = true;
  // ...
  return debounce(func, wait, {
    "leading": leading,
    "maxWait": wait,
    "trailing": trailing
  });
}

所以在 lodash 中,只需要 debounce 函數(shù)即可,throttle 相當(dāng)于 ”充話費(fèi)“ 送的。

至此,我們已經(jīng)解讀完 lodash 中的 debounce & throttle 函數(shù)源碼;

最后附帶一張 lodash 實現(xiàn)執(zhí)行效果圖,用來自測是否真的理解通透:

注:此圖取自于文章《 聊聊lodash的debounce實現(xiàn)》
6、小結(jié)

在前端領(lǐng)域的性能優(yōu)化手段中,防抖(debounce)和節(jié)流(throttle)是必備的技能,網(wǎng)上隨便一搜就有很多文章去分析解釋,不乏優(yōu)秀的文章使用 圖文混排 + 類比方式 深入淺出探討這兩函數(shù)的用法和使用場景(見文末的參考文檔)。

那我為什么還要寫這一篇文章?

緣起前兩天手動將 lodash 中的 debouncethrottle 兩個函數(shù) TS 化的需求,而平時我也只是使用并沒有在意它們真正的實現(xiàn)原理,因此在遷移過程我順帶閱讀了一番 lodash 中這兩個函數(shù)的源碼。

具體原因和遷移過程請移步《技巧 - 快速 TypeScript 化 lodash 中的 throttle & debounce 函數(shù)》

本文嘗試提供了另一個視角去解讀,通過時間軸 + 鬧鐘圖例 + 代碼的方式來解讀 lodash 中的 debounce & throttle 源碼;
整個流程下來只要理解了黑色、藍(lán)色、紅色這 3 種鬧鐘的關(guān)系,那么憑著理解力去實現(xiàn)簡版 lodashdebounce 函數(shù)并非難事。

當(dāng)然上述的敘述中,略過了很多細(xì)節(jié)和存在性的判斷(諸如 timeId 的存在性判斷、isInvoking的出現(xiàn)位置等),省略這主要是為了降低源碼閱讀的難度;(實際中這些細(xì)節(jié)的處理有時候反而很重要,是代碼健壯性不可或缺的一部分)

希望本文能對讀者理解 lodash 中的 debounce & throttle 源碼有些許的幫助,歡迎隨時關(guān)注微信公眾號或者技術(shù)博客留言交流。

【附】代碼片段

如果在你僅僅需要應(yīng)付簡單的一些場景,也可以直接使用下方的代碼片段。

A. 簡易 debounce - 只實現(xiàn) trailing 情況

防抖函數(shù)的概念:函數(shù)在 n 秒內(nèi)只執(zhí)行一次,若這 n 秒內(nèi),函數(shù)高頻觸發(fā),則會重新計算時間

將這段話翻譯成代碼,你會發(fā)現(xiàn)并不難:

//防抖代碼最簡單的實現(xiàn)
function debounce(func, wait) {
  let timerId, result;

  return function() {
    if(timerId){
      clearTimeout(timerId);  //  每次觸發(fā) 都清除當(dāng)前timer,重新設(shè)置時間
    }
    
    timerId = setTimeout(function(){
     result = func.apply(this, arguments);
    }, wait);
    
    return result;
  }
}

debounce 返回閉包(匿名函數(shù))

假如調(diào)用該閉包兩次:

如果調(diào)用兩次間隔 < wait 數(shù)值,先前調(diào)用會被 clearTimeout ,也就不執(zhí)行;最終只執(zhí)行 1 次調(diào)用(即第 2 次的調(diào)用)

如果調(diào)用兩次間隔 > wait 數(shù)值,當(dāng)執(zhí)行 clearTimeout 的時候,前一次調(diào)用已經(jīng)執(zhí)行了;所以最終這兩次調(diào)用都會執(zhí)行

上述的實現(xiàn),是最經(jīng)典的 trailing 情況,即以 wait 間隔結(jié)束點(diǎn)作為函數(shù)調(diào)用計時點(diǎn),是我們平時用的最多的場景

B. 簡易 debounce - 只實現(xiàn) leading 功能

另外用得比較多的就是以 wait 間隔開始點(diǎn)作為函數(shù)調(diào)用計時點(diǎn),即 leading 功能。

將上面代碼中最后的 setTimeout 內(nèi)容改成 timerId = undefined ,而將 fn.apply 提取出來加個 if 條件語句就行 ,修改后代碼如下:

//防抖代碼最簡單的實現(xiàn)
function debounce(func, wait) {
  let timerId, result;

  return function() {
    if(timerId){
      clearTimeout(timerId);  //  每次觸發(fā) 都清除當(dāng)前timer,重新設(shè)置時間
    }
    
    if(!timerId){
      result = fn.apply(this, arguments);
    }
    
    timerId = setTimeout(function() {
        timerId = undefined;
    }, wait);
    
    return result;
  }
}
fn.apply(lastThis, lastArgs) 之所以用 if 條件包裹,是針對首次調(diào)用的邊界情況

debounce 仍舊返回閉包(匿名函數(shù))

timerId 是閉包變量,相當(dāng)于標(biāo)志位,通過它可以知道某個函數(shù)的調(diào)用是否在上一次函數(shù)調(diào)用的影響范圍內(nèi)

假如調(diào)用該閉包兩次:

如果調(diào)用兩次間隔 < wait 數(shù)值,后調(diào)用因為仍在前一次的 wait 影響范圍內(nèi),所以會被 clearTimeout 掉;最終只執(zhí)行 1 次調(diào)用(即第 1 次的調(diào)用)

如果調(diào)用兩次間隔 > wait 數(shù)值,當(dāng)執(zhí)行第二次時 timerId 已經(jīng)是 underfined 的,所以會立即執(zhí)行 函數(shù),所以最終這兩次調(diào)用都會執(zhí)行

C. 簡易 throttle 函數(shù)

throttle 函數(shù)的概念:函數(shù)在 n 秒內(nèi)只執(zhí)行一次,若這 n 秒內(nèi)還在有函數(shù)調(diào)用的請求都直接被忽略掉。

實現(xiàn)原理也很簡單:定義開關(guān)變量 canRun,在定時開啟的這段時間內(nèi)控制這個開關(guān)變量為canRun = false上鎖),執(zhí)行完后才讓 canRun = true 即可。

  function throttle(func, wait) {
    let canRun = true
    return function () {
      if (!canRun) {
        return  // 如果開關(guān)關(guān)閉了,那就直接不執(zhí)行下邊的代碼
      }
      canRun = false // 持續(xù)觸發(fā)的話,run一直是false,就會停在上邊的判斷那里
      setTimeout(() => {
        func.apply(this, arguments)
        canRun = true // 定時器到時間之后,會把開關(guān)打開,我們的函數(shù)就會被執(zhí)行
      }, wait)
    }
  }
參考文章

Debouncing and Throttling Explained Through Examples:首推這篇經(jīng)典的文章,本文詳細(xì)描述了 lodash 中的 debounce 和 throttle 的思路設(shè)計;里面使用 圖文混排 深入淺出探討這兩函數(shù)的用法和具體使用場景,更為難得還嵌入有可交互 demo,能即刻感受這兩方法的具體使用方式;嫌看英文麻煩的可以看中文版 《實例解析防抖動(Debouncing)和節(jié)流閥(Throttling)》

防抖(debounce)函數(shù)的作用是什么?有哪些應(yīng)用場景,請實現(xiàn)一個防抖函數(shù):討論帖子,里面有不少的相關(guān)信息和資源

淺談 Underscore.js 中 _.throttle 和 _.debounce 的差異:很不錯的釋義文章,電梯類比秒懂

lodash.debounce: lodash debounce 多帶帶的庫,附官方文檔

防抖(debounce)函數(shù)的作用是什么:解釋了 debounce 函數(shù)的原理和實現(xiàn)

聊聊lodash的debounce實現(xiàn):作者對比了自己的實現(xiàn)和 lodash 中的實現(xiàn)

Confused about the maxWait option for LoDash’s debounce method:解釋 ‘maxWait’ 的作用

第 3 題:什么是防抖和節(jié)流?有什么區(qū)別?如何實現(xiàn):面試題,簡單快速實現(xiàn)防抖和節(jié)流這兩個函數(shù)

函數(shù)的防抖和節(jié)流是個啥???:用通俗的例子講解這兩個概念和實現(xiàn)

從lodash源碼學(xué)習(xí)節(jié)流與防抖:詳細(xì)注釋 lodash 中的 debounce 函數(shù)的實現(xiàn)

下面的是我的公眾號二維碼圖片,歡迎關(guān)注交流。

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

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

相關(guān)文章

  • 快速 TypeScript 化 lodash 中的 throttle &amp; debounce

    摘要:背景需要包寫起來爽,然而如果遇到?jīng)]有現(xiàn)成的化的工具函數(shù),就需要自己想辦法弄出一份類型聲明文件了。最為重要的是,這種遷移方面我們可以隨意自定義化中所需要的工具函數(shù),遷移粒度都可以由自己控制。 1、背景 1.1、需要 TS 包 TypeScript 寫起來爽,然而如果遇到?jīng)]有現(xiàn)成的 TS 化的工具函數(shù),就需要自己想辦法弄出一份類型聲明文件了。 前兩天要寫的小工具庫(Typescript 語...

    lewinlee 評論0 收藏0
  • JavaScript Debounce&amp;Throttle

    摘要:如果我們的回調(diào)函數(shù)較為復(fù)雜,頁面的性能就會變差。而可以保證穩(wěn)定的時間間隔執(zhí)行一次回調(diào)函數(shù)。但需要弄清楚的是,無論是還是,控制的都是回調(diào)函數(shù)的執(zhí)行,而不是事件的監(jiān)聽。 前言 假設(shè)現(xiàn)在有個需求:監(jiān)聽滑動事件,并執(zhí)行回調(diào)。當(dāng)你用觸摸板或者鼠標(biāo)滑動頁面時,每秒鐘大概會觸發(fā)幾十次scroll事件,而當(dāng)你在手機(jī)等移動終端上滑動頁面時,每秒就會觸發(fā)一百次scroll事件。如果我們的回調(diào)函數(shù)較為復(fù)雜,...

    The question 評論0 收藏0
  • 一次發(fā)現(xiàn)underscore源碼bug的經(jīng)歷以及對學(xué)術(shù)界拿來主義的思考

    摘要:事情是如何發(fā)生的最近干了件事情,發(fā)現(xiàn)了源碼的一個。樓主找到的關(guān)于和區(qū)別的資料如下關(guān)于拿來主義為什么這么多文章里會出現(xiàn)澤卡斯的錯誤代碼樓主想到了一個詞,叫做拿來主義。的文章,就深刻抨擊了拿來主義這一現(xiàn)象。 事情是如何發(fā)生的 最近干了件事情,發(fā)現(xiàn)了 underscore 源碼的一個 bug。這件事本身并沒有什么可說的,但是過程值得我們深思,記錄如下,各位看官仁者見仁智者見智。 平時有瀏覽別...

    Lionad-Morotar 評論0 收藏0
  • 【譯】通過例子解釋 Debounce 和 Throttle

    摘要:舉例舉例通過拖拽瀏覽器窗口,可以觸發(fā)很多次事件。不支持,所以不能在服務(wù)端用于文件系統(tǒng)事件??偨Y(jié)將一系列迅速觸發(fā)的事件例如敲擊鍵盤合并成一個單獨(dú)的事件。確保一個持續(xù)的操作流以每毫秒執(zhí)行一次的速度執(zhí)行。 Debounce 和 Throttle 是兩個很相似但是又不同的技術(shù),都可以控制一個函數(shù)在一段時間內(nèi)執(zhí)行的次數(shù)。 當(dāng)我們在操作 DOM 事件的時候,為函數(shù)添加 debounce 或者 th...

    LeoHsiun 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<