• 作用域
  • 闭包
  • 用闭包模拟私有方法
  • 使用闭包对数据进行缓存
  • 防抖
  • 节流(throttle)
  • 防抖和节流的应用场景
    ~ [实时搜索框。为了减少用户在输入的过程中不停地向后台发起请求。<a href="https://github.com/MacMang/JSBOOK/blob/master/%E9%97%AD%E5%8C%85/%E5%AE%9E%E6%97%B6%E6%9F%A5%E8%AF%A2.html">实时搜索框</a>](#实时搜索框。为了减少用户在输入的过程中不停地向后台发起请求。<a href="https://github.com/MacMang/JSBOOK/blob/master/%E9%97%AD%E5%8C%85/%E5%AE%9E%E6%97%B6%E6%9F%A5%E8%AF%A2.html">实时搜索框</a>)
    ~ [app列表使用向上滑动的过程中,向服务器发送请求。同样是为了避免多次请求,这里可以根据时间或者滑动的距离来作为防抖的判断条件。<a href="https://github.com/MacMang/JSBOOK/blob/master/%E9%97%AD%E5%8C%85/%E6%97%A0%E9%99%90%E6%BB%9A%E5%8A%A8%E5%8A%A0%E8%BD%BD.html">无限加载实例</a>](#app列表使用向上滑动的过程中,向服务器发送请求。同样是为了避免多次请求,这里可以根据时间或者滑动的距离来作为防抖的判断条件。<a href="https://github.com/MacMang/JSBOOK/blob/master/%E9%97%AD%E5%8C%85/%E6%97%A0%E9%99%90%E6%BB%9A%E5%8A%A8%E5%8A%A0%E8%BD%BD.html">无限加载实例</a>)
    

作用域

在前面的函数章节里,我们知道当一个函数在使用一个变量的时候,它首先会在自己的作用域里查看是否有该变量,如果没有,则继续往上级作用域查找是否有该变量

闭包

  • 闭包本质是个函数,当它访问父级函数的局部变量的时候机会产生闭包.
  • 作用: 将父级函数的局部变量状态存储到内存中

用闭包模拟私有方法

var returnNum = (function () {
    var num = 0;

    function changeNum(value) {
        num = value;
    }

    return {
        add: function () {
            changeNum(10);
        },
        delete: function () {
            changeNum(-10);
        },
        getNum: function () {
            return num;
        }
    }
})();

使用闭包对数据进行缓存

    //计算斐波那契数列 
    function fn(n){
        if(n==1||n==2){
            return 1;
        }
        return fn(n-1)+fn(n-2)
    }
    //保存计算结果
    function save(){
        //该对象用于存储计算的结果
        /*
            例如:求第45个
            result = {45:"计算结果"}
            然后再求 第40个
            result = {45:"45的计算结果",40:"40的计算结果"}
        */
        var result = {};
        return function(n){
            /*
                判断传递进来的n值是否为result对象的属性
                如果result[n]存在结果,则直接从对象中获取结果,
                如果不存在这先计算fn(n),然后再将fn(n)的结果赋值
                给result[n];

            */
            if(result[n]){
                return result[n]
            }else{
                return result[n]=fn(n)
            }
        }
    }

    var count = save();
    //计算第45个斐波那契数列
    // 第一次计算的时候,耗时非常久
    count(45);
    //第二次求第45个斐波那契数列,能够秒得结果
    count(45);

防抖

触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间

/*
    @description 防抖
    @params fn: 待执行的行为,n:预定执行的事件时间
*/
function debounce(fn, n) {    
    var timeout = null;    
    return function() {        
        if(timeout !== null)   clearTimeout(timeout);        
        timeout = setTimeout(fn, n);    
    }
}
// 处理函数
function handle() {    
    console.log(Math.random()); 
}
window.addEventListener('scroll', debounce(handle, 1000));

上面的代码中, 当给window对象注册scroll事件的时候,会马上触发debounce函数,该函数有两个参数,fn是待执行的行为,也就是当浏览器滚动的时候,你要做什么。n是你打算在浏览器滚动之后,多长时间触发fn待执行行为。

debounce函数体内部:

  1. 在debounce首次被触发的时候,在内存中定义了一个 timeout变量。
  2. 将一个函数作为返回值返回给函数调用者,当浏览器滚动的时候就会触发该函数。在该函数体内,首先判断内存中的timeout变量的值是否为空,如果不为空,就表示在之前已经有待执行事件。我们通过clearTimeout清除这个定时器,把该事件从待执行中移除。
  3. 重新用一个定时器,决定在n毫秒之后执行fn函数。并将生成的定时器赋值给timeout

节流(throttle)

当持续触发事件时,保证一定时间段内只调用一次事件处理函数

/*
    @description 节流
    @params func 待执行的行为,delay: 预定执行的事件时间,以毫秒计算
*/
var throttle = function(func, delay) {  
    //在内存中保留一个时间戳变量   
  var saveDate = Date.now();            
  return function() {                
    var _this = this;                
    var args = arguments;   
      // 获取回调函数执行时的事件             
    var now = Date.now();    
       // 判断回调函数执行时的时间戳和保留的时间戳之间的差是否大于delay的值,如果大于,则表示超过了节流控制的时间范围,func可以重新执行了            
    if (now - saveDate >= delay) {                    
          //重新执行func函数
      func.apply(_this, args);                    
          //将func函数执行时的时间戳保留在内存中,用于下次判断
      saveDate = Date.now();                
    }            
  }        
}        
function buySth() {            
  console.log("抢购商品了");        
}        
window.addEventListener('scroll', throttle(buySth, 10000));

防抖和节流的应用场景

以下例子,都在标题里有相应的下载链接,使用前端vue框架开发的,如果看不懂,可以暂时跳过,以后再回头阅读

实时搜索框。为了减少用户在输入的过程中不停地向后台发起请求。实时搜索框

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      list-style: none;
    }

    #app {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      overflow: scroll;
    }

    #app ul {
      width: 100%;
    }

    #app ul li {
      width: 100%;
      height: 100px;
      border-bottom: 1px solid lightgray;
      padding: 10px;
      box-sizing: border-box;
      display: flex;
      align-items: center;
    }

    .searchInput {
      width: 100%;
      height: 50px;
      border: 1px solid lightgray;
      padding: 10px;
      box-sizing: border-box;
    }

    .searchInput input {
      width: 100%;
      height: 100%;
    }
  </style>
  <script src="../bower_components/mockjs/dist/mock.js"></script>
  <script src="../bower_components/axios/dist/axios.js"></script>
  <script src="../bower_components/vue/dist/vue.js"></script>
  <script>
    const url = 'http://localhost:9999/table';
    const reg = /\/table(\/\w)*/
    const data = Mock.mock({
        'dataSource|100': [{
          'key|+1': 1,
          'name': '@name'
        }]
      })
    Mock.mock(reg, "get", (options) => {
      const searchValue = options.body?JSON.parse(options.body).searchValue: "";
      const dataSource = data.dataSource.filter((item)=>{
        const str = item.name.toLowerCase();
        return str.indexOf(searchValue.toLowerCase())!==-1
      })
      return {dataSource:dataSource};
    })
  </script>
</head>

<body>
  <div id="app">
    <div class="searchInput">
      <input type="text" v-model="searchValue" placeholder="搜索框" @input="startSearch">
    </div>
    <ul>
      <li v-for="(item,key) in dataSource" :key="key">
        {{item.key}}-{{item.name}}
      </li>
    </ul>
  </div>
</body>

</html>
<script>
  new Vue({
    el: '#app',
    data: {
      dataSource: [],
      searchValue: '',
      timer: null, //记录定时器的ID
    },
    mounted() {
      this.addData();
    },
    methods: {
      addData() {
        axios.get(url).then((resp) => {
          this.dataSource = [...this.dataSource, ...resp.data.dataSource]
        });
      },
      startSearch() {
        /*
          当在搜索框输入内容的时候,每输入一个字符就会发起一次请求,这会给服务器造成很大的压力
          解决方案:
          1. 监听onchange方法,在输入完成失去光标之后再查询
          2. 提供一个查询按钮,只有点击查询按钮的时候才开始查询
          但是上面的两种的方案,都满足不了我们实时查询的需求,所以需要在监听oninput事件的同时
          提供解决方案
          1. 通过防抖,如果短时间内输入多个字符,以最后一次输入为准进行请求
        */
        console.log(this.timer);
        //先清空上次生成的定时器
        clearTimeout(this.timer)
        //重新生成新的定时器,并将定时器的ID赋值给timer进行记录
        this.timer = setTimeout(()=>{
            //发起网络请求
            axios({
              method: "get",
              url: "/table",
              data: {
                searchValue: this.searchValue
              }
            }).then(resp => {
              //成功的回调函数,返回的是增加的数据
              this.dataSource = [...resp.data.dataSource]
              // 清空定时器
              clearTimeout(this.timer);
              this.timer = null;
            });
        },500)

    }
  },
  })
</script>

app列表使用向上滑动的过程中,向服务器发送请求。同样是为了避免多次请求,这里可以根据时间或者滑动的距离来作为防抖的判断条件。无限加载实例

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      list-style: none;
    }

    #app {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      overflow: scroll;
    }

    #app ul {
      width: 100%;
    }

    #app ul li {
      width: 100%;
      height: 100px;
      border-bottom: 1px solid lightgray;
    }
  </style>
  <script src="../bower_components/mockjs/dist/mock.js"></script>
  <script src="../bower_components/axios/dist/axios.js"></script>
  <script src="../bower_components/vue/dist/vue.js"></script>
  <script>
    const url = 'http://localhost:9999/table';
    Mock.mock(url, {
      'dataSource|10': [{
        'key|+1': 1,
        'name': '@name'
      }]
    })
  </script>
</head>

<body>
  <div id="app" ref="app">
    <ul>
      <li v-for="item in dataSource" :key="item.key">
        {{item.key}}-{{item.name}}
      </li>
    </ul>
    <!-- <button @click="addData">加载</button> -->
  </div>
</body>

</html>
<script>
  new Vue({
    el: '#app',
    data: {
      dataSource: []
    },
    mounted() {
      this.addData();
      const loadData = this.scroll().loadData.bind(this);
      app.addEventListener('scroll',loadData)
    },
    methods: {
      addData() {
        console.log("发起网络请求");
        axios.get(url).then((resp) => {
          this.dataSource = [...this.dataSource, ...resp.data.dataSource]
        });
      },
      scroll() {
        // 计算每个表有多高
        let ulHeight = 10 * 101;
        //app框的高度
        let appHeight = this.$refs['app'].clientHeight;
        //页码
        let liNum = 1;
        let min = Math.abs((appHeight * liNum - ulHeight));
        let saved = false;
        return {
          loadData(ev) {
              const scrollTop = ev.target.scrollTop;
              /*
                第一种情况,滑动到底部的时候再进行加载
                if(Math.abs(min)===scrollTop){
                  liNum+=1;
                  min += ulHeight;
                  this.addData();
                }
              */

              /*
                第二种情况:滚动距离min还有200的时候就发起请求
              */
              if (min === scrollTop) {
                saved = false
              }
              if (min <= (scrollTop + 200)) {
                //判断是否已经请求过数据,并且
                if (saved) {
                  console.log("已经发起请求过了,不需要重新发起请求");
                  return;
                }
                this.addData();
                saved = true;
                min += ulHeight;
              }
          },
          // 手动释放 闭包所占用的内存
          cleanHeap(){
            ulHeight = null;
            appHeight = null;
            min = null;
            saved = null;
          }
        }
      }
    },
  })
</script>
  1. 浏览器窗口大小改变后,只需窗口调整完后,再执行 resize 事件中的代码,防止重复渲染。
  2. 表单校验。
  3. 一定时间范围内,只允许用户点击一次按钮。过了这个时间范围,用户可以继续点击。

results matching ""

    No results matching ""