js高级04-深浅拷贝、防抖节流

5、深浅拷贝

5.1、数据类型

数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型。

基本数据类型的特点:直接存储在栈(stack)中的数据

引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

5.2、深浅拷贝区别

深浅拷贝主要区别在复杂数据类型,对于基本数据类型,没有区别,改变拷贝的数据,都不会改变原数据

浅拷贝(shallow copy):

浅拷贝只拷贝引用(地址值),当拷贝的新对象发生改变时,原对象也会发生相同的改变,也就是说,浅拷贝会影响原来的元素

深拷贝(deep copy):

每一级的数据都会拷贝 ,拷贝后,两个对象拥有不同的地址,当拷贝出来的对象发生改变时,原对象内容不会改变,两者互不影响

5.3、实现浅拷贝

5.3.1、直接赋值法

    var arr = [1,2,3]
    var newarr = arr;
    newarr[1] = 5;
    console.log(arr,newarr);//[1, 5, 3],[1, 5, 3]

5.3.2、Object.assign()

   <script>
      // 1.Object.assign()
      var obj = {
        name: "jack",
        age: 18,
        person: {
          name: "tim",
          age: 28,
        },
      };
      //将{}和obj合并,返回一个新的obj对象
      var objNew = Object.assign({}, obj);
      objNew.name = "tom";//当object只有一层的时候,是深拷贝
      objNew.person.name = "Diana";//对object多层的数据,是浅拷贝
      console.log(obj, "obj");
      console.log(objNew, "objNew");
    </script>

5.3.3、Array.prototype.concat()

//3、Array.prototype.concat()
      var arr2 = [1, "hello", { usename: "tom" }];
      var arr3 = arr2.concat();
      arr2[2].usename = "tim";
      console.log(arr2[2].usename, "arr2"); //tim
      console.log(arr3[2].usename, "arr3"); //tim

5.3.4、Array.prototype.slice()

      var arr4 = [1, "hello", { usename: "tom" }];
      var arr5 = arr4.slice();
      arr4[2].usename = "diana";
      //console.log(arr4[2].usename, "arr4"); //diana
      //console.log(arr5[2].usename, "arr5"); //diana

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

5.4、实现深拷贝

5.4.1、Object.assign()

当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。

<script>
      //1、Object.assign();
      var obj = {
        //当object只有一层的时候,是深拷贝
        name: "jack",
        age: 18,
      };
      var objNew = Object.assign({}, obj);
      objNew.name = "tom";
      console.log(obj.name, "obj"); //jack
      console.log(objNew.name, "objNew"); //tom
    </script>

5.4.2、JSON.parse(JSON.stringify())

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝

注意:这种方法虽然可以实现数组或对象深拷贝,但不能处理函数

  <script>
      var arr1 = [
        1,
        "hello",
        { usename: "tom" },
        function () {
          console.log(111);
        },
      ];
      var arr2 = JSON.parse(JSON.stringify(arr1));
      arr1[2].usename = "diana";
      console.log(arr1[2].usename); //diana
      console.log(arr2[2].usename); //tom
      console.log(arr2[3]);//null
    </script>

5.4.3、手写递归方法

原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

    <script>
      function deepClone(oldData) {
        // 1、判断oldData的数据类型
        if (typeof oldData == "object" && oldData !== null) {
          // 3、再判断类型是否是数组,根据类型返回[],{},开辟新的空间,放深拷贝数据
          var res = Array.isArray(oldData) ? [] : {};
          // 4、遍历数据,拿到属性,将属性值赋值给拷贝的数据
          for (var k in oldData) {
            // 5、判断这个k属性是否是oldData上自有的属性,原型链上的就不算了
            if (oldData.hasOwnProperty(k)) {
              // 6、对象有可能是嵌套对象,所以,需要递归再进行判断,一层层,直到拷贝了所有的数据
              res[k] = deepClone(oldData[k]);
            }
          }
          return res;
        } else {
          // 2、如果不是对象或数组,则返回原数据
          return oldData;
        }
      }

      //  测试:
      let obj = {
        name: "jack",
        age: 18,
        hobby: ["song", "run"],
        sayHi(msg) {
          console.log(msg);
        },
      };
      let newObj = deepClone(obj);
      // obj.sayHi("hi");
      // newObj.sayHi("hello");
      newObj.name = "tom";
      newObj.hobby = ["唱歌", "跑步"];
      console.log(obj, "obj");
      console.log(newObj, "newObj");
    </script>

5.4.4、通过jQuery的extend方法实现深拷贝 

var array = [1,2,3,4];
var newArray = $.extend(true,[],array);

5.4.5、lodash函数库实现深拷贝

lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝

6、防抖节流

6.1、节流和防抖的目的

都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,防止在短时间内频繁触发同一事件而出现延迟,假死或卡顿的现象

6.2、节流和防抖的区别

防抖:如果不断在delay之前重新触发,那么定时器会不断重新计时,最终会在最后一次完后才执行

节流:目前有一事件A设置了定时器,那么在delay之前触发,都只会触发一次

6.3、节流和防抖的详解

(1)、防抖 debounce(设置1分钟只会执行一次,如果1分钟内又多次触发,会从再次触发开始重新计算1分钟时间,然后再执行)

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

本质:将多次执行变为最后一次执行(重新执行)

举例:比如我们平时在使用搜索框时,我们一输入内容就会发送对应的网络请求,如果我们后面一直有在输入内容,那么就会一直发送网络请求。正确的做法应该是在我们输入期间不发送网络请求,当我们输入完成后在发送网络请求。

(2)、节流 throttle(设置1分钟只会执行一次,一分钟内,多次触发无效,必须等1分钟后才能触发函数)

高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

本质:将多次执行变成每隔一段时间执行(不能打断我)

举例:比如我们在玩LOL游戏时,当需要回城时,我们触发回城按钮,进入到回城状态进会有一个等待的时间,如果在这个等待时间内有重复去执行回城按钮,这时回城状态并不会做出其他响应,而是等时间到了后才会完成回城。这也说明了,在我们回城的过程中,一直触发回城按钮都是不会响应的,他会按照自己的一个回城时间才做出响应。

6.4、代码实现简单防抖与节流

6.4.1、防抖

6.4.1.1、简单版(掌握)
  <body>
    <!-- 防抖函数 debounce -->
    <input type="text" name="" id="" />

    <script>
      var int = document.querySelector("input");
      function inputChange() {
        console.log(this.value);
      }
      // 不加防抖函数,不断的输出
      //int.addEventListener("keyup", inputChange);

      //加入防抖函数,1秒钟输出一次
      var intVal = debounce(inputChange, 1000);
      int.addEventListener("keyup", intVal);

      //防抖函数
      function debounce(fn, delay) {
        // 1、定义一个定时器,保存上一次的定时器
        var timer = null;
        // 2、真正执行的函数
        var _debounce = function () {
          //3、 取消上一次的定时器
          if (timer) clearTimeout(timer);
          //4、 保存this
          var _this = this;
          //5、 延迟执行
          timer = setTimeout(function () {
            //6、 让fn执行时,指向input
            fn.call(_this);
          }, delay);
        };
        return _debounce;
      }
    </script>
  </body>   

6.4.1.2、this和参数实现(了解) 

 <input type="text" name="" id="" />
    <script>
      var int = document.querySelector("input");
      // 需求:输出输入的内容
      function inputChange() {
        console.log(this.value);
      }
      // 不加防抖函数,不停的搜索
      // int.addEventListener("keyup", inputChange);
      // 加入防抖函数
      int.addEventListener("keyup", debounce(inputChange, 1000));

      //  防抖函数二:this和参数实现

      function debounce(fn, delay) {
        //console.log(this, "debounce"); //这里的this指向的是window
        // 1、定义一个定时器,保存上一次的定时器
        var timer = null;
        // 2、真正执行的函数,传入参数
        return function (...ages) {
          //3、保存this,此时的this指向的是input
          var _this = this;
          // 4、判断定时器是否存在,清楚定时器
          if (timer) clearTimeout(timer);
          // 重新调用setTimerout
          timer = setTimeout(function () {
            //console.log(this);//定时器里的this指向widow
            // fn()直接执行,this指向window
            fn.apply(_this, ages);
            timer = null;
          }, delay);
        };
      }
    </script>

6.4.1.3、立即执行(了解) 

<input type="text" name="" id="" value="你好" />
    <script>
      var int = document.querySelector("input");
      // 需求:输出输入的内容
      function inputChange() {
        console.log(this.value);
      }
      // 不加防抖函数,不停的搜索
      // int.addEventListener("keyup", inputChange);
      // 加入防抖函数
      int.addEventListener("keyup", debounce(inputChange, 1000, true));
      //  int.addEventListener("keyup", debounce(inputChange, 1000, false));

      //  防抖函数三:立即执行

      //创建一个防抖函数debounce
      function debounce(fn, delay, immediate = false) {
        // 1.定义一个定时器, 保存上一次的定时器
        let timer = null;
        let isInvoke = false;
        // 2.真正执行的函数
        const _debounce = function (...ages) {
          // 取消上一次的定时器
          if (timer) clearTimeout(timer);

          // 判断是否需要立即执行
          if (immediate && !isInvoke) {
            fn.apply(this, ages);
            isInvoke = true;
          } else {
            // 延迟执行
            timer = setTimeout(() => {
              // 外部传入的真正要执行的函数
              fn.apply(this, ages);
              isInvoke = false;
            }, delay);
          }
        };

        return _debounce;
      }
    </script>

6.4.2、节流

6.4.2.1、时间戳版(了解)
  <body>
    <!-- 需求:在快速点击的过程中,降低日志打印的频率,1s中执行一次 -->

    <button>点我试试</button>
    <script>
      var btn = document.querySelector("button");
      var fn = function () {
        console.log("发送请求");
      };
      // 问题:快速点击按钮,只要点了一次,日志就打印一次
      // btn.addEventListener("click", fn);
      // 添加节流函数,在2s内,多次点击,只执行一次
      btn.addEventListener("click", throttle(fn, 2000));


      //参数:  fn 真正执行的函数,interval 多久执行一次
      function throttle(fn, interval) {
        // 1、记录上一次的开始时间
        var lastTime = 0;
        // 2、事件触发时,真正执行的函数
        var _throttle = function () {
          // 2.1获取当前事件触发时的时间
          var nowTime = new Date().getTime();
          // 2.2使用规定好的时间间隔减去当前时间和上一次触发时间的时间间隔,得到多少时间再次触发
          var remainTime = interval - (nowTime - lastTime);
          if (remainTime <= 0) {
            // 2.3触发真正函数
            fn();
            // 2.4 保留上次触发的时间
            lastTime = nowTime;
          }
        };
        return _throttle;
      }
    </script>
  </body>
6.4.2.2、定时器版(掌握)
   <body>
    <!-- 需求:在快速点击的过程中,降低日志打印的频率,1s中执行一次 -->
    <button>点我试试</button>
    <script>
      var btn = document.querySelector("button");
      var fn = function () {
        console.log("发送请求");
      };
      // 问题:快速点击按钮,只要点了一次,日志就打印一次
      // btn.addEventListener("click", fn);
      // 添加节流函数,在2s内,多次点击,只执行一次
      btn.addEventListener("click", throttle(fn, 2000));

      //参数:  fn 真正执行的函数,interval 多久执行一次
      // 定时器方式
      function throttle(fn, delay) {
        //1、设置标志,判断函数是否执行
        var sign = true;
        return function () {
          //2、 在函数开头判断标志是否为 true,不为 true 则中断函数
          if (!sign) return;
          //3、  sign 设置为 false,防止执行之前再被执行
          sign = false;
          //4、 保存this
          var _this = this;
          setTimeout(function () {
            //5、 this是当前被点击的dom元素,button
            fn.apply(_this, arguments);
            //6、 执行完事件之后,重新将这个标志设置为 true
            sign = true;
          }, delay);
        };
      }
    </script>
  </body>

6.5、应用场景

  • 防抖
    • 表单元素的校验,如手机号,邮箱,用户名等,部分搜索功能的模糊查询结果实现
    • 搜索框搜素输入
    • 文本编辑器实时保存
  • 节流
    • 高频事件,例如快速点击、鼠标滑动、resize事件、scroll事件
    • 下拉加载
    • 视频播放记录时间等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值