js專題系列-防抖和節流

1.前言

通常來講,這一段主要是講一些知識的大致概況,都不是那麼重要的,至關於文章的摘要。可是就是有不一樣尋常的,好比本文對於防抖以及節流的概念理解就很重要,很是重要。javascript

1.1 出現緣由

首先須要指出的是爲何會出現這2種思想。css

1.因爲肉眼只能分辨出必定頻率的變化,也就是說一種變化1s內變化1000次和變成60次對人的感官是同樣的,同理,能夠類推到js代碼。在必定時間內,代碼執行的次數不必定要很是多。達到必定頻率就足夠了。由於跑得越多,帶來的效果也是同樣。html

2.客戶端的性能問題。衆所周知,就目前來講兼容對應前端來講仍是至關重要的,而主要的兼容點在於低端機型,因此說咱們有必要把js代碼的執行次數控制在合理的範圍。既能節省瀏覽器CPU資源,又能讓頁面瀏覽更加順暢,不會由於js的執行而發生卡頓。前端

以上就是函數節流和函數防抖出現的主要緣由。vue

1.2 概念理解

上面說了那麼多,只是爲了說明爲何會出現防抖和節流這2種實現,下面再來形象理解一下這兩種思想的不一樣之處,不少時候我都會把這兩種思想混淆,因此此次特地想了很好記住的辦法。java

1.函數節流 是指必定時間內js方法只跑一次。jquery

節流節流就是節省水流的意思,就想水龍頭在流水,咱們能夠手動讓水流(在必定時間內)小一點,可是他會一直在流。ajax

固然還有一個形象的比喻,開源節流,就好比咱們這個月(在必定時間內)咱們少花一點錢,可是咱們天天仍是都須要花錢的。json

2.函數防抖 只有足夠的空閒時間,才執行代碼一次。瀏覽器

好比生活中的坐公交,就是必定時間內,若是有人陸續刷卡上車,司機就不會開車。只有別人沒刷卡了,司機纔開車。(其實只要記住了節流的思想就能經過排除法判斷節流和防抖了)

2.代碼

2.1 防抖

上面的解釋都是爲了形象生動地說明防抖和節流的思想以及區別,如今咱們須要從代碼層面來進一步探索防抖。

首先寫代碼以前最重要的事情就是想在腦子裏面想這段代碼須要實現什麼邏輯,下面就是防抖的代碼邏輯思路。

你儘管觸發事件,可是我必定在事件觸發 n 秒後才執行,若是你在一個事件觸發的 n 秒內又觸發了這個事件,那我就以新的事件的時間爲準,n 秒後才執行,總之,就是要等你觸發完事件 n 秒內再也不觸發事件,我才執行!

好了,根據上面的思路咱們能夠很輕鬆地寫出初版防抖的代碼。

function debounce(func, waitTime) {
  var timeout;
  return function () {
    clearTimeout(timeout)
    timeout = setTimeout(func, waitTime);
  }
}
document.querySelector('#app').onmousemove = debounce(fn, 1000);
複製代碼

上面的一小段代碼就是最原始的防抖代碼。

能夠看到上面這幾行代碼就用到了閉包的知識,主要的目的就是爲了在函數執行後保留timeout這個變量。

想讓一個函數執行完後,函數內的某個變量(timer)仍舊保留,就可使用閉包把要保存的變量在父做用域聲明,其餘的語句放到子做用域裏,而且做爲一個function返回。下面的很大實例代碼都用到了閉包來解決保留變量的問題。

還有一點也許有小夥伴會有疑惑。爲何這裏要返回一個函數呢。其實很好理解,咱們能夠來看下面的代碼

var timeout;
function debounce(func, waitTime) {
  clearTimeout(timeout)
  timeout = setTimeout(func, waitTime);
}
container.onmousemove = debounce(getUserAction, 1000);
複製代碼

我手動刪掉了debounce函數裏面的return ,而後爲了保留timeout,我把它放到了全局變量,這幾行代碼看起來和上面的很像,可是你能夠直接跑一下這段代碼,發現debounce只會執行一次!!!

哈哈哈,其實之因此在debounce函數裏面返回一個函數,那是由於onmousemove須要的是綁定的函數,咱們的測試代碼執行一遍後只會返回undefined ,至關於

container.onmousemove = debounce(getUserAction, 1000);
container.onmousemove = undefined;
複製代碼

固然就沒有正確綁定事件了。若是從好理解的角度來寫,其實也是能夠想下面這樣綁定的

var timeout;
function debounce(func, waitTime) {
  clearTimeout(timeout)
  timeout = setTimeout(func, waitTime);
}
container.onmousemove = () => {
  debounce(getUserAction, 1000);
}
複製代碼

下面全部方法的道理都是和第一個函數同樣的。

可是這一版本的代碼咱們在fn中打印this以及event對象,發現有點不對。

能夠從上圖中看到,fn中的this以及event對象,發現並非但願的,因此咱們須要手動把this以及event對象傳遞給fn函數。因而乎有了下面第二版的防抖函數。

function debounce(func, waitTime) {
  var timeout;
  return function () {
    var context = this,
        args = arguments;
    clearTimeout(timeout)
    timeout = setTimeout(function () {
      func.apply(context, args)
    },  waitTime);
  }
}
複製代碼

其實也就是用了apply函數把this以及event對象傳遞給fn函數。

2.2 節流

下面讓咱們繼續來看一下節流思想的代碼邏輯。

使用時間戳,當觸發事件的時候,咱們取出當前的時間戳,而後減去以前的時間戳(最一開始值設爲 0 ),若是大於設置的時間週期,就執行函數,而後更新時間戳爲當前的時間戳,若是小於,就不執行。

ok,根據上面的邏輯,咱們能夠很輕鬆寫出初版節流函數。

function throttle(func, waitTime) {
    var context, 
        args,
        previous = 0;
    return function() {
        var now = + new Date();
            context = this;
            args = arguments;
        if (now - previous > waitTime) {
            func.apply(context, args);
            previous = now;
        }
    }
}
複製代碼

或者咱們其實還能夠藉助定時器來實現節流。

當觸發事件的時候,咱們設置一個定時器,再觸發事件的時候,若是定時器存在,就不執行,直到定時器執行,而後執行函數,清空定時器,這樣就能夠設置下個定時器。

function throttle(func, waitTime) {
    var timeout,
        previous = 0;
    return function() {
        context = this;
        args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args)
            }, waitTime)
        }

    }
}
複製代碼

3. 知識轉爲技能

2018年,我最大的感悟就是儘可能把所學的知識轉爲技能(來自老姚 [juejin.im/post/5c34ab…](2018年收穫5條認知,條條振聾發聵 | 掘金年度徵文))

知識是能夠學到的,可是技能只能習得。

上面兩部分咱們都是在學防抖和節流出現的緣由,對應的概念以及實現的思想邏輯,這些都是知識,如今就讓咱們一塊兒把學到的知識轉爲技能,爭取成爲本身項目的一部分吧。

對於像防抖和節流這種工具性質的函數,咱們大能夠把他們放在公共文件裏面,而後在須要的地方直接調用就能夠了。

防抖和節流最大的核心用處在於優化代碼性能,能夠用在不少地方,好比輸入框的驗證,圖片懶加載,各類頻繁觸發的DOM事件等等。

下面是我本身模擬寫了一個百度搜索的按鈕精靈,圖一是沒有用防抖搜索 我是 這個關鍵詞發現發起了N屢次請求,而後改了一行代碼加入了防抖,請求的狀況就變成了圖二。效果顯而易見。

this.debounce(this.getData, 1000)();
複製代碼

附上源碼

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>百度按鍵精靈</title>
  <style type="text/css">
    * {
      margin: 0;
      padding: 0;
    }

    .search-box {
      width: 640px;
      margin: 50px auto;
    }

    .search-logo {
      width: 270px;
      margin: 0 auto;
    }

    .search-logo img {
      width: 100%;
    }

    .search-main {
      height: 80px;
    }

    .serch-main input {
      height: 100%;
    }

    .search-con {
      float: left;
      width: 522px;
      height: 20px;
      padding: 9px 7px;
      font: 16px arial;
      border: 1px solid #b8b8b8;
      border-bottom: 1px solid #ccc;
      border-right: 0;
      vertical-align: top;
      outline: none;
    }

    .search-con:focus {
      border-color: #38f;
    }

    .active {
      border-color: #38f;
    }

    .search-btn {
      float: left;
      cursor: pointer;
      width: 102px;
      height: 40px;
      border: 0;
      background: none;
      background-color: #38f;
      font-size: 16px;
      color: white;
      font-weight: normal;
    }

    .search-spirit {
      line-height: 24px;
      border: 1px solid #ccc;
      list-style: none;
    }

    .search-spirit a {
      display: block;
      padding-left: 7px;
      color: #000;
      text-decoration: none;
    }

    .search-active {
      background: #f2f2f2;
    }
  </style>
</head>

<body>
  <div class="search-box">
    <div class="search-logo">
      <img src="https://user-gold-cdn.xitu.io/2019/1/12/16840f8015b1bbd4?w=540&h=258&f=png&s=3706" />
    </div>
    <div class="search-main">
      <input type="text" class="search-con" v-model="searchKey1" v-on:keyup="getData1" />
      <input type="button" value="百度一下" class="search-btn" v-on:click='go()' />
      <ul class="search-spirit">
        <li v-for="data in searchList"><a href="javascript:;" v-html="data.q" v-bind:class="{'search-active':$index==nowIndex}"
            v-on:mouseover="activeHover($index)" v-on:mouseout="leaveHover()" v-on:click="listGo(data.q)"></a></li>
      </ul>
    </div>
  </div>
  <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
  <script src="http://cdn.bootcss.com/vue/1.0.7/vue.js"></script>
  <script>
    let vue = new Vue({
      el: 'body',
      data: {
        searchKey: '',
        searchKey1: '',
        nowIndex: -1,
        historyArr: [],
        searchList: []
      },
      ready: function () {
        var storage = window.localStorage,
          obj = new Object();
        if (storage.getItem('baidu') == null) {
          storage.setItem('baidu', JSON.stringify(obj));//對象轉字符串
        }
      },
      methods: {
        getData1: function() {
          <!--這是修改的調用方法-->
          this.debounce(this.getData, 1000)();
        },
        getData: function () {
          var oThis = this;
          oThis.searchKey = oThis.searchKey1;
          this.nowIndex = -1;
          $.ajax({
            type: "post",
            url: "http://suggestion.baidu.com/su?&wd=" + oThis.searchKey + "&json=1&p=3&cb=aa",
            dataType: 'jsonp',
            jsonp: 'aa',
            jsonpCallback: 'aa',
            success: function (data) {
              if (data.g) {
                oThis.searchList = data.g;
              }
            },
            error: function () {
              console.log('接口報錯,請撥打110');
            }
          });
          if (oThis.searchKey === '') {
            oThis.getHistory();
          }
        },
        go: function () {
          this.searchKey = this.searchKey1;
          this.setHistory(this.searchKey);
          window.location.href = 'https://www.baidu.com/s?wd=' + this.searchKey;
        },
        listGo: function (q) {
          this.setHistory(q);
          window.location.href = 'https://www.baidu.com/s?wd=' + q;
        },
        keyGo: function (event) {
          if (event.keyCode == 13) {
            this.go();
          }
          if (event.keyCode === 40) {
            this.nowIndex++;
            this.nowIndex === this.searchList.length ? this.nowIndex = 0 : '';
            this.searchKey1 = this.searchList[this.nowIndex].q;
          }
          if (event.keyCode === 38) {
            this.nowIndex--;
            this.nowIndex === -1 ? this.nowIndex = this.searchList.length - 1 : '';
            this.searchKey1 = this.searchList[this.nowIndex].q;
          }
        },
        none: function () {
          setTimeout(() => {
            this.searchList = null;
            this.nowIndex = -1;
          }, 100);
        },
        activeHover: function (index) {
          this.nowIndex = index;
        },
        leaveHover: function () {
          this.nowIndex = -1;
        },
        setHistory: function (strKey) {
          var nowtime = (new Date()).getTime(),
            storage = window.localStorage,
            obj = JSON.parse(storage.getItem('baidu'));//字符串轉對象
          if (!obj[strKey]) {
            obj[strKey] = nowtime;
            storage.setItem('baidu', JSON.stringify(obj));//對象轉字符串
          }
        },
        getHistory: function () {
          var storage = window.localStorage,
            length = 10,
            arr = [],
            obj = {},
            newObj = {};
          this.searchList = [];
          obj = JSON.parse(storage.getItem('baidu'));//字符串轉對象
          for (var x in obj) {
            arr.push(obj[x]);
          }
          arr.sort(function (a, b) {
            return b - a;
          });
          arr.length >= 10 ? length = 10 : length = arr.length;
          for (var i = 0; i < length; i++) {
            for (var x in obj) {
              if (obj[x] === arr[i]) {
                this.searchList.push({ q: x });
                newObj[x] = arr[i];
              }
            }
          }
          storage.setItem('baidu', JSON.stringify(newObj));//對象轉字符串
        },
        
        <!--這是新加的防抖函數-->
        debounce(func, wait) {
          var timeout;
          return function () {
            var context = this;
            var args = arguments;
            clearTimeout(timeout)
            timeout = setTimeout(function () {
              func.apply(context, args)
            }, wait);
          }
        }
      }
    });
  </script>
</body>

</html>
複製代碼

本文完

相關文章
相關標籤/搜索