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>
复制代码

本文完

相关文章
相关标签/搜索