【JS进阶】第四天

本文介绍了JavaScript中浅拷贝和深拷贝的区别,包括Object.assign的使用、递归实现深拷贝、JSON.stringify在深拷贝中的作用,以及函数中this的动态指定方法(call、apply、bind)。同时讨论了防抖和节流的概念及其在实际场景中的应用。
摘要由CSDN通过智能技术生成

JavaScript 进阶 - 第 4 天

深浅拷贝

浅拷贝

首先浅拷贝和深拷贝只针对引用类型,因为简单类型直接拷贝值了

浅拷贝:拷贝的是地址,只拷贝一层

直接赋值的情况:
在这里插入图片描述

浅拷贝的情况:
在这里插入图片描述

常见方法:

  1. 拷贝对象:Object.assgin() / 展开运算符 {…obj} 拷贝对象
  2. 拷贝数组:Array.prototype.concat() 或者 […arr]

如果是简单数据类型拷贝值,如果是引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题),也就是只拷贝第一层

里面的属性值是简单数据类型就直接拷贝值;
如果属性值是引用数据类型则拷贝的是地址;

补充Object.assign的使用

如果只有一个参数,Object.assign会直接返回该参数。
要使用 Object.assign() 方法来实现浅拷贝,你需要确保将目标对象作为第一个参数传递给 Object.assign(),然后将源对象作为后续参数传递。这样 Object.assign() 将会将源对象的所有可枚举属性复制到目标对象中,从而实现浅拷贝。

下面是一个示例:

const obj1 = { name: "Alice" };
const obj2 = Object.assign({}, obj1);

console.log(obj2); // { name: "Alice" }
console.log(obj1 === obj2); // false,因为它们是两个不同的对象

在这个例子中,我们创建了一个空对象 {} 作为目标对象,然后将 obj1 的属性复制到这个目标对象中,最终得到了 obj2。这样就实现了对 obj1 的浅拷贝。

深拷贝

首先浅拷贝和深拷贝只针对引用类型

深拷贝:拷贝的是对象,不是地址

常见方法:

  1. 通过递归实现深拷贝
  2. js 的库:lodash/cloneDeep
  3. 通过 JSON.stringify()实现
递归实现深拷贝

函数递归:

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数

  • 简单理解:函数内部自己调用自己, 这个函数就是递归函数
  • 递归函数的作用和循环效果类似
  • 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
<body>
  <script>
    const obj = {
      uname: "pink",
      age: 18,
      hobby: ["乒乓球", "足球"],
      family: {
        baby: "小pink",
      },
    };
    const o = {};
    // 拷贝函数
    function deepCopy(newObj, oldObj) {
      debugger;
      for (let k in oldObj) {
        // k 是属性名,oldObj[k]是属性值
        // 处理数组的问题  一定先写数组 在写 对象 不能颠倒,因为一切皆对象,要是把对象写前面,Array就永远没有机会了
        if (oldObj[k] instanceof Array) {
          newObj[k] = [];
          //  newObj[k] 接收 []  hobby
          //  oldObj[k]   ['乒乓球', '足球']
          deepCopy(newObj[k], oldObj[k]);
        } else if (oldObj[k] instanceof Object) {
          newObj[k] = {};
          deepCopy(newObj[k], oldObj[k]);
        } else {
         // 只有一层的简单数据
          //  k  属性名 uname age    oldObj[k]  属性值  18
          // newObj[k]  === o.uname  给新对象添加属性
          newObj[k] = oldObj[k];
        }
      }
    }
    deepCopy(o, obj); // 函数调用  两个参数 o 新对象  obj 旧对象
    console.log(o);
    o.age = 20;
    o.hobby[0] = "篮球";
    o.family.baby = "老pink";
    console.log(obj);
    console.log([1, 23] instanceof Object);
    // 复习
    // const obj = {
    //   uname: 'pink',
    //   age: 18,
    //   hobby: ['乒乓球', '足球']
    // }
    // function deepCopy({ }, oldObj) {
    //   // k 属性名  oldObj[k] 属性值
    //   for (let k in oldObj) {
    //     // 处理数组的问题   k 变量
    //     newObj[k] = oldObj[k]
    //     // o.uname = 'pink'
    //     // newObj.k  = 'pink'
    //   }
    // }
  </script>
</body>
    const arr = [1, 2];
 // 处理数组的问题  一定先写数组 在写 对象 不能颠倒,因为一切皆对象,要是把对象写前面,Array就永远没有机会了
    console.log(arr instanceof Array);  // true
    console.log(arr instanceof Object);  // true
js 库 lodash 里面 cloneDeep 内部实现了深拷贝
<body>
  <!-- 先引用 -->
  <script src="./lodash.min.js"></script>
  <script>
    const obj = {
      uname: "pink",
      age: 18,
      hobby: ["乒乓球", "足球"],
      family: {
        baby: "小pink",
      },
    };
    const o = _.cloneDeep(obj); // 写法规范就是下划线  _.
    console.log(o);
    o.family.baby = "老pink";
    console.log(obj);
  </script>
</body>
JSON 序列化

JSON.stringify(obj) 把对象转换成JSON字符串
JSON.parse(obj) 把字符串转换成对象

<body>
  <script>
    const obj = {
      uname: "pink",
      age: 18,
      hobby: ["乒乓球", "足球"],
      family: {
        baby: "小pink",
      },
    };
    // 把对象转换为 JSON 字符串
    // console.log(JSON.stringify(obj))
    const o = JSON.parse(JSON.stringify(obj));
    console.log(o);
    o.family.baby = "123";
    console.log(obj);
  </script>
</body>

异常处理

了解 JavaScript 中程序异常处理的方法,提升代码运行的健壮性。

throw

异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行

总结:

  1. throw 抛出异常信息,程序也会终止执行
  2. throw 后面跟的是错误提示信息
  3. Error 对象配合 throw 使用,能够设置更详细的错误信息
<script>
  function counter(x, y) {
    if (!x || !y) {
      // throw '参数不能为空!';
      throw new Error("参数不能为空!");
    }

    return x + y;
  }

  counter();
</script>

总结:

  1. throw 抛出异常信息,程序也会终止执行
  2. throw 后面跟的是错误提示信息
  3. Error 对象配合 throw 使用,能够设置更详细的错误信息

try … catch

<script>
  function foo() {
    try {
      // 查找 DOM 节点
      const p = document.querySelector(".p");
      p.style.color = "red";
    } catch (error) {
      // try 代码段中执行有错误时,会执行 catch 代码段
      // 查看错误信息
      console.log(error.message);
      // 终止代码继续执行
      return;
    } finally {
      alert("执行");
    }
    console.log("如果出现错误,我的语句不会执行");
  }
  foo();
</script>

总结:

  1. try...catch 用于捕获错误信息
  2. 将预估可能发生错误的代码写在 try 代码段中
  3. 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息

debugger

相当于断点调试
在这里插入图片描述

处理 this

了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法。

this 是 JavaScript 最具“魅惑”的知识点,不同的应用场合 this 的取值可能会有意想不到的结果,在此我们对以往学习过的关于【 this 默认的取值】情况进行归纳和总结。

普通函数

普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】,如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }
  // 函数表达式
  const sayHello = function () {
    console.log(this);  
  };
  // 函数的调用方式决定了 this 的值
  sayHi(); // window
  window.sayHi();

  // 普通对象
  const user = {
    name: "小明",
    walk: function () {
      console.log(this);
    },
  };
  // 动态为 user 添加方法
  user.sayHi = sayHi;
  uesr.sayHello = sayHello;
  // 函数调用方式,决定了 this 的值
  user.sayHi();  // user
  user.sayHello();  // user
</script>

注: 普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined

箭头函数

箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this。 箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。
在这里插入图片描述

<script>
  console.log(this); // 此处为 window
  // 箭头函数
  const sayHi = function () {
    console.log(this); // 该箭头函数中的 this 为函数声明环境中 this 一致
  };
  // 普通对象
  const user = {
    name: "小明",
    // 该箭头函数中的 this 为函数声明环境中 this 一致
    walk: () => {
      console.log(this); // 箭头函数本身没有this,所以往上找,user是对象,也没有this,所以继续向上找,所以最后是window
    },

    sleep: function () {
      let str = "hello";
      console.log(this);
      let fn = () => {
        console.log(str);
        console.log(this); // 该箭头函数中的 this 与 sleep 中的 this 一致
      };
      // 调用箭头函数
      fn();
    },
  };

  // 动态添加方法
  user.sayHi = sayHi;

  // 函数调用
  user.sayHi();
  user.sleep();
  user.walk();
</script>

在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此 DOM 事件回调函数不推荐使用箭头函数,如下代码所示:

  <body>
    <button id="pu">click普通函数</button>
    <button id="jian">click箭头函数</button>
  </body>
  <script>
    document.querySelector("#pu").addEventListener("click", function () {
      console.log("click普通函数", this);  // button
    });
    document.querySelector("#jian").addEventListener("click", () => {
      console.log("箭头函数", this);  // window
    });
  </script>
<script>
  // DOM 节点
  const btn = document.querySelector(".btn");
  // 箭头函数 此时 this 指向了 window
  btn.addEventListener("click", () => {
    console.log(this);
  });
  // 普通函数 此时 this 指向了 DOM 对象
  btn.addEventListener("click", function () {
    console.log(this);
  });
</script>

同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数,如下代码所示:
构造函数和原型的this都指向实例对象

<script>
  function Person() {}
  // 原型对像上添加了箭头函数
  Person.prototype.walk = () => {
    console.log("人都要走路...");
    console.log(this); // window
  };
  const p1 = new Person();
  p1.walk();
</script>

在这里插入图片描述

改变 this 指向

以上归纳了普通函数和箭头函数中关于 this 默认值的情形,不仅如此 JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向:

call (了解即可

作用:调用函数 + 改变this指向
在这里插入图片描述

使用 call 方法调用函数,同时指定函数中 this 的值,使用方法如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }

  let user = {
    name: "小明",
    age: 18,
  };

  let student = {
    name: "小红",
    age: 16,
  };

  // 调用函数并指定 this 的值
  sayHi.call(user); // this 值为 user
  sayHi.call(student); // this 值为 student

  // 求和函数
  function counter(x, y) {
    return x + y;
  }

  // 调用 counter 函数,并传入参数
  let result = counter.call(null, 5, 10);
  console.log(result);
</script>

总结:

  1. call 方法能够在调用函数的同时指定 this 的值
  2. 使用 call 方法调用函数时,第 1 个参数为 this 指定的值
  3. call 方法的其余参数会依次自动传入函数做为函数的参数
  4. 返回值就是函数的返回值
apply

在这里插入图片描述

使用 apply 方法调用函数,同时指定函数中 this 的值,使用方法如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }

  let user = {
    name: "小明",
    age: 18,
  };

  let student = {
    name: "小红",
    age: 16,
  };

  // 调用函数并指定 this 的值
  sayHi.apply(user); // this 值为 user
  sayHi.apply(student); // this 值为 student

  // 求和函数
  function counter(x, y) {
    return x + y;
  }
  // 调用 counter 函数,并传入参数
  let result = counter.apply(null, [5, 10]);
  console.log(result);
</script>

总结:

  1. apply 方法能够在调用函数的同时指定 this 的值
  2. 使用 apply 方法调用函数时,第 1 个参数为 this 指定的值
  3. apply 方法第 2 个参数为数组,数组的单元值依次自动传入函数做为函数的参数
  4. 返回值就是函数的返回值
bind ( 重点

在这里插入图片描述

bind 方法并不会调用函数,而是创建一个指定了 this 值的新函数,使用方法如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }
  let user = {
    name: "小明",
    age: 18,
  };
  // 调用 bind 指定 this 的值
  let sayHello = sayHi.bind(user);
  // 调用使用 bind 创建的新函数
  sayHello();
</script>

在这里插入图片描述

注:bind 方法创建新的函数,与原函数的唯一的变化是改变了 this 的值。

小结

三者区别:

  1. 参数形式:call 和bind 是直接穿参数 ,apply是数组的形式传递参数
  2. 是否会调用函数:call 和apply 会调用;但是bind不会调用
  3. 返回值:bind返回由指定的this和初始化参数改造的新函数(原函数拷贝); 而call和bind的返回值是函数的返回值
    在这里插入图片描述

防抖节流

防抖(debounce)

概念

单位时间内,频繁出发事件,只执行一次,且只执行最后一次
只要被打断就重新来。
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

使用场景:
  1. 搜索框搜索输入,只需用户最后一次输入完,再发送请求
  2. 手机号、邮箱验证输入
代码实战

在这里插入图片描述

节流(throttle)

概念

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数,(只执行第一次

使用场景

在这里插入图片描述

代码实战

在这里插入图片描述
在这里插入图片描述

总结

在这里插入图片描述

【节流】综合案例——跳转到历史播放时刻

在这里插入图片描述onloaddata也可以理解成 打开页面触发事件

但是ontimeupdate的出发频次太高了,需要节流,可以设置1秒钟触发一次

在这里插入图片描述
黑马讲解视频

在这里插入图片描述

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值