[JavaScript] 函數(shù)節(jié)流(throttle)和函數(shù)防抖(debounce)

js 的函數(shù)節(jié)流(throttle)和函數(shù)防抖(debounce)概述

函數(shù)防抖(debounce)

一個(gè)事件頻繁觸發(fā)有额,但是我們不想讓他觸發(fā)的這么頻繁形娇,于是我們就設(shè)置一個(gè)定時(shí)器讓這個(gè)事件在 xxx 秒之后再執(zhí)行哆键。如果 xxx 秒內(nèi)觸發(fā)了,則清理定時(shí)器,重置等待事件 xxx 秒
比如在拖動(dòng) window 窗口進(jìn)行 background 變色的操作的時(shí)候,如果不加限制的話亭螟,隨便拖個(gè)來回會(huì)引起無限制的頁(yè)面回流與重繪
或者在用戶進(jìn)行 input 輸入的時(shí)候,對(duì)內(nèi)容的驗(yàn)證放在用戶停止輸入的 300ms 后執(zhí)行(當(dāng)然這樣不一定好骑歹,比如銀行卡長(zhǎng)度驗(yàn)證不能再輸入過程中及時(shí)反饋)

一段代碼實(shí)現(xiàn)窗口拖動(dòng)變色

  <script>
    let body = document.getElementsByTagName("body")[0];
    let index = 0;
    if (body) {
      window.onresize = function() {
        index++;
        console.log("變色" + index + "次");
        let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
        body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")"; //晃瞎眼睛
      };
    }
  </script>

簡(jiǎn)單的防抖

使用 setTimeout 進(jìn)行延遲處理预烙,每次觸發(fā)事件時(shí)都清除掉之前的方法

  <script>
    let body = document.getElementsByTagName("body")[0];
    let index = 0;
    let timer = null;
    if (body) {
      window.onresize = function() {
        //如果在一秒的延遲過程中再次觸發(fā),就將定時(shí)器清除,清除完再重新設(shè)置一個(gè)新的
        clearTimeout(timer);

        timer = setTimeout(function() {
          index++;
          console.log("變色" + index + "次");
          let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
          body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
        }, 1000); //反正就是等你什么都不干一秒后才會(huì)執(zhí)行代碼
      };
    }
  </script>

抽離 debounce

但是目前有一個(gè)問題道媚,就是代碼耦合扁掸,這樣不夠優(yōu)雅(b 格),將防抖和變色分離一下

  <script>
    let body = document.getElementsByTagName("body")[0];
    let index = 0;
    let lazyLayout = debounce(changeBgColor, 1000);
    window.onresize = lazyLayout;

    function changeBgColor() {
      index++;
      console.log("變色" + index + "次");
      let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
      body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
    }

    //函數(shù)去抖(連續(xù)事件觸發(fā)結(jié)束后只觸發(fā)一次)
    function debounce(func, wait) {
      let timeout, context, args; //默認(rèn)都是undefined

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

        if (timeout) clearTimeout(timeout);

        timeout = setTimeout(function() {
          //執(zhí)行的時(shí)候到了
          func.apply(context, args);
        }, wait);
      };
    }
  </script>

underscore.js 的 debounce

underscore.js 實(shí)現(xiàn)的 debounce 已經(jīng)經(jīng)過人民群眾的檢驗(yàn)

//1.9.1
 _.debounce = function(func, wait, immediate) {
    var timeout, result;

    var later = function(context, args) {
      timeout = null;
      if (args) result = func.apply(context, args);
    };

    var debounced = restArguments(function(args) {
      if (timeout) clearTimeout(timeout);
      if (immediate) {
        var callNow = !timeout;
        timeout = setTimeout(later, wait);
        if (callNow) result = func.apply(this, args);
      } else {
        timeout = _.delay(later, wait, this, args);
      }

      return result;
    });

    debounced.cancel = function() {
      clearTimeout(timeout);
      timeout = null;
    };

    return debounced;
  };

函數(shù)節(jié)流(throttle)

簡(jiǎn)單的節(jié)流

一個(gè)事件頻繁觸發(fā)最域,但是在 xxx 秒內(nèi)只能執(zhí)行一次代碼

//上面的變色在節(jié)流中就是這樣寫了
  <script>
    let doSomething = true;
    let body = document.getElementsByTagName("body")[0];
    let index = 0;

    window.onresize = function() {
      if (!doSomething) return;
      doSomething = false;
      setTimeout(function() {
        index++;
        console.log("變色" + index + "次");
        let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
        body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
        doSomething = true;
      }, 1000);
    };
  </script>

分離出 throttle 函數(shù)

跟上面的防抖差不多谴分,隨便分離一下,降低代碼的耦合度

<script>
    let body = document.getElementsByTagName("body")[0];
    let index = 0;
    let lazyLayout = throttle(changeBgColor, 1000);
    window.onresize = lazyLayout;

    function changeBgColor() {
      index++;
      console.log("變色" + index + "次");
      let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
      body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
    }

    //函數(shù)去抖(連續(xù)事件觸發(fā)結(jié)束后只觸發(fā)一次)
    function throttle(func, wait) {
      let context,
        args,
        doSomething = true;

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

        if (!doSomething) return;

        doSomething = false;

        setTimeout(function() {
          //執(zhí)行的時(shí)候到了
          func.apply(context, args);
          doSomething = true;
        }, wait);
      };
    }
  </script>

underscore.js 中 throttle 函數(shù)實(shí)現(xiàn)

  _.throttle = function(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };

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

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

    return throttled;
  };

防抖和節(jié)流是用來干啥的镀脂?

防抖的用處

  • 綁定 scroll 滾動(dòng)事件,resize 監(jiān)聽事件
  • 鼠標(biāo)點(diǎn)擊牺蹄,執(zhí)行一個(gè)異步事件,相當(dāng)于讓用戶連續(xù)點(diǎn)擊事件只生效一次(很有用吧)
  • 還有就是輸入框校驗(yàn)事件(但是不一定好使薄翅,比如校驗(yàn)銀行卡長(zhǎng)度沙兰,當(dāng)你輸入完之后已經(jīng)超出 100 個(gè)字符虑省,正常應(yīng)該是超出就提示錯(cuò)誤信息)

節(jié)流的用處

  • 當(dāng)然還是鼠標(biāo)點(diǎn)擊啦,但是這個(gè)是限制用戶點(diǎn)擊頻率僧凰。類似于你拿把 ak47 射擊探颈,槍的射速是 100 發(fā)/分鐘,但是的手速達(dá)到 1000 按/分鐘,就要限制一下嘍(防止惡意刷子)
  • 根據(jù)屏幕滾動(dòng)到底部加載更多的功能

其實(shí)二者主要就是為了解決短時(shí)間內(nèi)連續(xù)多次重復(fù)觸發(fā)和大量的 DOM 操作的問題训措,來進(jìn)行性能優(yōu)化(重點(diǎn)是同時(shí)還能接著辦事伪节,并不耽誤)
防抖主要是一定在 xxx 秒后執(zhí)行,而節(jié)流主要是在 xxx 內(nèi)執(zhí)行(時(shí)間之后,時(shí)間之內(nèi))

右邊那個(gè)快速目錄就是加了個(gè) throttle绩鸣,控制臺(tái)的執(zhí)行速度就減少了(快速目錄是看了掘金的目錄之后弄的怀大,確實(shí)方便了好多,對(duì)于長(zhǎng)文本的閱讀體驗(yàn)好了不少)

文章寫的時(shí)候用的 underscore 1.8.2 版本呀闻,實(shí)現(xiàn)也是參考 underscore 的源碼化借,實(shí)現(xiàn)方式與 underscore 最新有些代碼還是不太一樣了。(功能還是相同的)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捡多,一起剝皮案震驚了整個(gè)濱河市蓖康,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌垒手,老刑警劉巖蒜焊,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異科贬,居然都是意外死亡泳梆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門榜掌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來优妙,“玉大人,你說我怎么就攤上這事憎账√着穑” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鼠哥,是天一觀的道長(zhǎng)熟菲。 經(jīng)常有香客問我看政,道長(zhǎng)朴恳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任允蚣,我火速辦了婚禮于颖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚷兔。我一直安慰自己森渐,他們只是感情好做入,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著同衣,像睡著了一般竟块。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耐齐,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天浪秘,我揣著相機(jī)與錄音,去河邊找鬼埠况。 笑死耸携,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辕翰。 我是一名探鬼主播夺衍,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼喜命!你這毒婦竟也來了沟沙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤壁榕,失蹤者是張志新(化名)和其女友劉穎尝胆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體护桦,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡含衔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了二庵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贪染。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖催享,靈堂內(nèi)的尸體忽然破棺而出杭隙,到底是詐尸還是另有隱情,我是刑警寧澤因妙,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布痰憎,位于F島的核電站,受9級(jí)特大地震影響攀涵,放射性物質(zhì)發(fā)生泄漏铣耘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一以故、第九天 我趴在偏房一處隱蔽的房頂上張望蜗细。 院中可真熱鬧,春花似錦、人聲如沸炉媒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吊骤。三九已至缎岗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間白粉,已是汗流浹背密强。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜗元,地道東北人或渤。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像奕扣,于是被迫代替她去往敵國(guó)和親薪鹦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容