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

資訊專欄INFORMATION COLUMN

Debounce vs Throttle

xcold / 2757人閱讀

摘要:那么還有最后一個(gè)問題,那我之前設(shè)置的定時(shí)器怎么辦呢定時(shí)器執(zhí)行的是這個(gè)函數(shù),而這個(gè)函數(shù)又會(huì)通過進(jìn)行一次判斷。

我們?cè)谔幚硎录臅r(shí)候,有些事件由于觸發(fā)太頻繁,而每次事件都處理的話,會(huì)消耗太多資源,導(dǎo)致瀏覽器崩潰。最常見的是我們?cè)谝苿?dòng)端實(shí)現(xiàn)無限加載的時(shí)候,移動(dòng)端本來滾動(dòng)就不是很靈敏,如果每次滾動(dòng)都處理的話,界面就直接卡死了。

因此,我們通常會(huì)選擇,不立即處理事件,而是在觸發(fā)一定次數(shù)或一定時(shí)間之后進(jìn)行處理。這時(shí)候我們有兩個(gè)選擇: debounce(防抖動(dòng))和 throttle(節(jié)流閥)。

之前看過很多文章都還是沒有太弄明白兩者之間的區(qū)別,最后通過看源碼大致了解了兩者之間的區(qū)別以及簡單的實(shí)現(xiàn)思路。

首先,我們通過實(shí)踐來最簡單的看看二者的區(qū)別:

可以看到,throttle會(huì)在第一次事件觸發(fā)的時(shí)候就執(zhí)行,然后每隔wait(我這里設(shè)置的2000ms)執(zhí)行一次,而debounce只會(huì)在事件結(jié)束之后執(zhí)行一次。

有了一個(gè)大概的印象之后,我們看一看lodash的源碼對(duì)debouncethrottle的區(qū)別。

這里討論默認(rèn)情況

function throttle(func, wait, options) {
  let leading = true,
    trailing = true;

  if (typeof func !== "function") {
    throw new TypeError(FUNC_ERROR_TEXT);
  }
  if (typeof options === "object") {
    leading = "leading" in options
      ? !!options.leading
      : leading;
    trailing = "trailing" in options
      ? !!options.trailing
      : trailing;
  }
  return debounce(func, wait, {
    leading,
    maxWait: wait,
    trailing,
  });
}

可以看到,throttle最后返回的還是debounce函數(shù),只是指定了options選項(xiàng)。那么接下來我們就集中分析debounce。

function debounce(fn, wait, options) {
    var lastArgs,
      lastThis,
          maxWait,
          result,
          timerId,
          lastCallTime,
          lastInvokeTime = 0,
          leading = false,
          maxing = false,
          trailing = true;
      function debounced() {
        var time = now(),
            isInvoking = shouldInvoke(time);

        lastArgs = arguments;
        lastThis = this;
        lastCallTime = time;

        if (isInvoking) {
          if (timerId === undefined) {
            return leadingEdge(lastCallTime);
          }
          if (maxing) {
            // Handle invocations in a tight loop.
            timerId = setTimeout(timerExpired, wait);
            return invokeFunc(lastCallTime);
          }
        }
        if (timerId === undefined) {
          timerId = setTimeout(timerExpired, wait);
        }
        return result;
      }
      debounced.cancel = cancel;
      debounced.flush = flush;
      return debounced;
}

為了記錄每次執(zhí)行的相關(guān)信息,debounce函數(shù)最后返回的是一個(gè)函數(shù),形成一個(gè)閉包。

這也解釋了為什么這樣寫不行:

    window.addEventListener("resize", function(){
      _.debounce(onResize, 2000);
    });

這樣寫根本就不會(huì)調(diào)用內(nèi)部的debounced函數(shù)。

解決第一個(gè)不同

debounced內(nèi)部呢,首先記錄了當(dāng)前調(diào)用的時(shí)間,然后通過shouldInvoke這個(gè)函數(shù)判斷是否應(yīng)該調(diào)用傳入的func

      function shouldInvoke(time) {
        var timeSinceLastCall = time - lastCallTime,
            timeSinceLastInvoke = time - lastInvokeTime;

        // Either this is the first call, activity has stopped and we"re at the
        // trailing edge, the system time has gone backwards and we"re treating
        // it as the trailing edge, or we"ve hit the `maxWait` limit.
        return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
          (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
      }

可以看到,該函數(shù)返回true的幾個(gè)條件。其中需要我們引起注意的是最后一個(gè)條件,這是debouncethrottle的區(qū)別之一。

首先maxing通過函數(shù)開始的幾行代碼判斷:

      if (isObject(options)) {
        leading = !!options.leading;
        maxing = "maxWait" in options;
        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
        trailing = "trailing" in options ? !!options.trailing : trailing;
      }

我們看到,在定義throttle的時(shí)候, 給debounce函數(shù)給傳入了options, 而里面包含maxWait這個(gè)屬性,因此,對(duì)于throttle來說,maxingtrue, 而沒有傳入optionsdebounce則為false。這就是二者區(qū)別之一。在這里決定了shouldInvoke函數(shù)返回的值,以及是否執(zhí)行接下去的邏輯判斷。

我們?cè)倩氐?b>debounced這個(gè)函數(shù):

  if (isInvoking) {
          if (timerId === undefined) {
            return leadingEdge(lastCallTime);
          }
          if (maxing) {
            // Handle invocations in a tight loop.
            timerId = setTimeout(timerExpired, wait);
            return invokeFunc(lastCallTime);
          }
        }
        if (timerId === undefined) {
          timerId = setTimeout(timerExpired, wait);
        }

在第一次調(diào)用的時(shí)候,debouncethrottleisInvoking
true, 且此時(shí)timerId === undefined也成立,就返回leadingEdge(lastCallTime)這個(gè)函數(shù)。

那么我們?cè)賮砜纯?b>leadingEdge 這個(gè)函數(shù);

      function leadingEdge(time) {
        // Reset any `maxWait` timer.
        lastInvokeTime = time;
        // Start the timer for the trailing edge.
        timerId = setTimeout(timerExpired, wait);
        // Invoke the leading edge.
        return leading ? invokeFunc(time) : result;
      }

這里出現(xiàn)了debouncethrottle的第二個(gè)區(qū)別。這個(gè)函數(shù)首先是設(shè)置了一個(gè)定時(shí)器,隨后返回的結(jié)果由leading決定。在默認(rèn)情況下,throttle傳入的leadingtrue,而debouncefalse。因此,throttle會(huì)馬上執(zhí)行傳入的函數(shù),而debounce不會(huì)。

這里我們就解決了它們的第一個(gè)不同:throttle會(huì)在第一次調(diào)用的時(shí)候就執(zhí)行,而debounce不會(huì)。

解決第二個(gè)不同

我們?cè)倩氐?b>shouldInvoke的返回條件那里,如果在一個(gè)時(shí)間內(nèi)頻繁的調(diào)用, 前面三個(gè)條件都不會(huì)成立,對(duì)于debounce來說,最后一個(gè)也不會(huì)成立。而對(duì)于throttle來說,首先maxingtrue, 而如果距離上一次*傳入的func 函數(shù)調(diào)用 大于maxWait最長等待時(shí)間的話,它也會(huì)返回true

function shouldInvoke(time) {
        var timeSinceLastCall = time - lastCallTime,
            timeSinceLastInvoke = time - lastInvokeTime;

        return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
          (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
      }
        if (isInvoking) {
          if (timerId === undefined) {
            return leadingEdge(lastCallTime);
          }
          if (maxing) {
            // Handle invocations in a tight loop.
            timerId = setTimeout(timerExpired, wait);
            return invokeFunc(lastCallTime);
          }
        }

那么在shouldInvoke成立之后,throttle會(huì)設(shè)置一個(gè)定時(shí)器,返回執(zhí)行傳入函數(shù)的結(jié)果。

這就是debouncethrottle 之間的第二個(gè)區(qū)別:throttle會(huì)保證你每隔一段時(shí)間都會(huì)執(zhí)行,而debounce不會(huì)。

那么還有最后一個(gè)問題,那我之前設(shè)置的定時(shí)器怎么辦呢?

timerId = setTimeout(timerExpired, wait);

定時(shí)器執(zhí)行的是timerExpired這個(gè)函數(shù),而這個(gè)函數(shù)又會(huì)通過shouldInvoke進(jìn)行一次判斷。

function timerExpired() {
        var time = now();
        if (shouldInvoke(time)) {
          return trailingEdge(time);
        }
        // Restart the timer.
        timerId = setTimeout(timerExpired, remainingWait(time));
      }

最后,傳入的func怎么執(zhí)行的呢?下面這個(gè)函數(shù)實(shí)現(xiàn):

function invokeFunc(time) {
        var args = lastArgs,
            thisArg = lastThis;

        lastArgs = lastThis = undefined;
        lastInvokeTime = time;
        result = func.apply(thisArg, args);
        return result;
      }
餓了么的簡單實(shí)現(xiàn)

在看餓了么的infinite scroll這個(gè)源碼的時(shí)候,看到了一個(gè)簡單版本的實(shí)現(xiàn):

var throttle = function (fn, delay) {
  var now, lastExec, timer, context, args; 

  var execute = function () {
    fn.apply(context, args);
    lastExec = now;
  };

  return function () {
    context = this;
    args = arguments;

    now = Date.now();

    if (timer) {
      clearTimeout(timer);
      timer = null;
    }

    if (lastExec) {
      var diff = delay - (now - lastExec);
      if (diff < 0) {
        execute();
      } else {
        timer = setTimeout(() => {
          execute();
        }, diff);
      }
    } else {
      execute();
    }
  };
};

那么它的思路很簡單:

通過lastExec判斷是否是第一次調(diào)用,如果是,就馬上執(zhí)行處理函數(shù)。

隨后就會(huì)監(jiān)測,每次調(diào)用的時(shí)間與上次執(zhí)行函數(shù)的時(shí)間差,如果小于0,就立馬執(zhí)行。大于0就會(huì)在事件間隔之后執(zhí)行。

每次調(diào)用的時(shí)候都會(huì)清除掉上一次的定時(shí)任務(wù),這樣就會(huì)保證只有一個(gè)最近的定時(shí)任務(wù)在等待執(zhí)行。

那么它與lodash的一個(gè)最大的區(qū)別呢,就是它是關(guān)注與上次執(zhí)行處理函數(shù)的時(shí)間差, 而lodashshouldInvoke關(guān)注的是兩次事件調(diào)用函數(shù)的時(shí)間差。

總結(jié)

總的來說,這種實(shí)現(xiàn)的主要部分呢,就是時(shí)間差定時(shí)器

最后,自己參照寫了簡單的debouncethrottle: Gist求指教!

參考資料

debouncing-throttling-explained-examples | CSS-Tricks

Lodash源碼

餓了么 vue-infinite-scroll

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

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

相關(guān)文章

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

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

    Lionad-Morotar 評(píng)論0 收藏0
  • throttledebounce的區(qū)別

    摘要:自己嘗試一下年在的文章中第一次看到的實(shí)現(xiàn)方法。這三種實(shí)現(xiàn)方法內(nèi)部不同,但是接口幾乎一致。如你所見,我們使用了參數(shù),因?yàn)槲覀冎粚?duì)用戶停止改變?yōu)g覽器大小時(shí)最后一次事件感興趣。 前幾天看到一篇文章,我的公眾號(hào)里也分享了《一次發(fā)現(xiàn)underscore源碼bug的經(jīng)歷以及對(duì)學(xué)術(shù)界拿來主義的思考》具體文章詳見,微信公眾號(hào):showImg(https://segmentfault.com/img/b...

    Pluser 評(píng)論0 收藏0
  • 【譯】通過例子解釋 DebounceThrottle

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

    LeoHsiun 評(píng)論0 收藏0
  • JS進(jìn)階篇3---函數(shù)“節(jié)流” VS “防抖”

    摘要:目的都是為了降低回調(diào)函數(shù)執(zhí)行頻率,節(jié)省計(jì)算機(jī)資源,優(yōu)化性能,提升用戶體驗(yàn)。函數(shù)防抖事件頻繁觸發(fā)的情況下,只有經(jīng)過足夠的空閑時(shí)間,才執(zhí)行代碼一次。 函數(shù)節(jié)流和函數(shù)防抖的對(duì)比分析 一、前言 前端開發(fā)中,函數(shù)節(jié)流(throttle) 和 函數(shù)防抖(debounce) 作為常用的性能優(yōu)化方法,兩者都是用于優(yōu)化高頻率執(zhí)行 js 代碼的手段,那具體它們有什么異同點(diǎn)呢?有對(duì)這兩個(gè)概念不太了解的小伙伴...

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

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

0條評(píng)論

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