JavaScript - 进阶+高级(笔记)

前言

  • 给孩子点点关注吧!😭
    image.png
  • 本篇文章主要记录以下几部分:
    • 进阶:
      • 作用域;
      • 函数进阶(函数提升、函数参数、箭头函数);
      • 解构赋值;
      • 对象进阶(构造函数、实例成员、静态成员);
      • 内置构造函数(Object、Array、String、Number);
      • 编程思想;
      • 构造函数;
      • 原型
    • 高级:
      • 深浅拷贝;
      • 异常处理;
      • this指向;
      • 性能优化(防抖、节流);

  • 以下部分请移步JavaScript - 基础+WebAPI(笔记)
    • 基础:
      • 输入输出语法;
      • 数据类型;
      • 运算符;
      • 流程控制 - 分支语句;
      • 流程控制 - 循环语句;
      • 数组 - 基础;
      • 函数 - 基础;
      • 对象 - 基础;
    • Web API:
      • DOM;
      • DOM事件基础(事件监听、常用事件、事件对象);
      • DOM事件进阶(事件流、事件委托);
      • 日期对象;
      • 节点操作;
      • M端事件;
      • JS插件;
      • window对象;
      • 本地存储;
      • 正则表达式;

叁、❗❗ JavaScript进阶

一、 作用域

  • 作用域(scope) = 全局作用域(Global) + 局部作用域(Local)
  • 局部作用域 = 函数作用域 + 块作用域
  • 作用域: 规定了变量 能够 被访问 的 范围,离开了这个范围,变量便不能被访问
    • 变量的作用范围

1.1 局部作用域

  • Local
  • 局部作用域 = 函数作用域 + 块作用域

1.1.1 函数作用域

  • 注意:
    • 函数内部 声明的 变量 只能在 函数内部访问,外部 无法直接访问(可以利用闭包来访问)
    • 函数参数 也是 函数内部局部变量
    • 不同函数 内部声明 的 变量 无法 互相访问
    • ⚠ 函数执行完毕后,函数内部的变量实际被清空了(JS垃圾回收机制)

1.1.2 块作用域

  • 在JavaScript中使用 {} 包裹的 代码 称为 代码块,代码块内部 声明的变量 外部【有可能:var声明的可以】无法访问
    • 代码展示:
      // 用 let 声明的变量 有 块作用域
      for (let i = 0; i < 5; i++) {
        console.log(i);
      }
      console.log(i); // i is not defined
      -------------------------------------------------------------------
      // 用 var 声明的变量 没有 块作用域
      for (var i = 0; i < 5; i++) {
        console.log(i);
      }
      console.log(i); // 5 
      
  • 注意:
    • letconst 声明的变量 会产生 块作用域
    • var 🔺不会产生🔻 块作用域
      • var 会被 函数作用域 所限制
    • 不同代码块之间 的 变量 无法 互相访问
    • 推荐使用 letconst

1.2 全局作用域

  • Global
  • 注意:
    • window 对象 动态添加的 属性 默认 是 全局变量,不推荐!
    • 函数中 未使用 任何关键字 声明的 变量 是 全局变量,不推荐!!!
    • 尽可能减少声明全局变量,防止变量污染

1.3 作用域链

  • 作用域: 规定了变量 能够 被访问 的 范围,离开了这个范围,变量便不能被访问
    • 变量的作用范围
  • 嵌套关系作用域 串联 起来形成了 作用域链
  • 本质: 底层 变量查找机制
    • 在函数执行时,会 优先 在 当前 函数作用域 中 查找变量
    • 如果 当前作用域 查找不到 则会 依次逐级 查找 父级作用域 直到 全局作用域
    • 代码展示:
      // 作用域链
      // 本质上就是底层(最小的那个作用域) 变量的查找机制
      // 变量访问机制:就近原则,由内向外
      // 全局作用域
      let a = 1;
      let b = 2;
      // 局部作用域
      function f() {
          let a = 1;
          let b = 3;
          // 局部作用域
          function g() {
              a = 2;
              let c = 4;
              console.log(a);
          }
          g();   // 调用g
      }
      f();   //调用f	// 2
      
  • 注意:
    • 相同作用域链 中按照 从小到大 的规则查找变量
    • ⚠🔺 作用域 能够访问 作用域,作用域 无法访问 作用域
      • 见上面代码展示:g函数 可以 log b,f函数 无法 log c

1.4 JS垃圾回收机制 - GC

  • JS中 内存的分配回收 都是 自动完成 的,内存 不使用 的时候 会被 垃圾回收器 自动回收
    • 内存泄漏: 不再用到的内存,没有及时释放
  • 内存的生命周期
    • 1️⃣ 内存分配: 声明 变量函数对象的时候,系统会 自动 为它们 分配内存
    • 2️⃣ 内存使用:读写内存,也就是 使用 变量、函数等
    • 3️⃣ 内存回收: 使用完毕,由垃圾回收器 自动回收 不再使用的内存
    • 注意:
      • 全局变量 一般 不会回收(关闭页面回收)
      • ⚠ 一般情况下 局部变量 的值,不用了就会被自动回收
  • 垃圾回收算法说明:
    • 1️⃣❌ 引用计数法
      • 致命缺陷:嵌套引用
      • 如果两个对象相互引用,尽管他们不再使用,垃圾回收器不会进行回收,导致内存泄漏
      • image-20220819165530801
    • 2️⃣ 标记清除法
      • image-20220819165552870

1.5 ❗❗ 闭包

  • 闭包:Closure

  • 概念: 一个函数 对 周围状态的引用 捆绑在一起,内层函数 能够访问到 其外层函数 的 作用域

    • 闭包是一种使用过程

    • 闭包 = 内层函数 + 外层函数的变量

    • 代码展示:

      function fn() {
        let b = 9;
        function outer() {
          const a = 1;
          console.log(b);
          function f() {
            console.log(a);
            console.log(b);
          }
          f();
        }
        outer();
      }
      fn();
      
      • 函数套函数 不一定会 产生 闭包
      • 如果 内层函数 用到 外层函数变量 这种情况就 会产生闭包
      • image-20220812214431189
  • 闭包使用注意: 闭包使用的时候,内部的变量因为被外部引用了,所以代码执行完毕不会释放内存 - 内存泄漏(引用计数法)

  • 闭包作用:

    • 实现数据的私有化
    • 外部 可以 访问 函数内部 变量
    • 允许将 函数 与其所操作的 某些数据(环境)关联起来
  • 应用场景:

    • 防抖
    • 节流(定时器)
  • 基本格式:

    function fn() {
      let b = 9;
      function outer() {
        const a = 1;
        console.log(b);
        function f() {
          let c = 6;
          console.log(a);
          console.log(b);
        }
        return f;	// function 复杂数据类型,返回出去的是指向函数的地址
      }
      return outer;
    }
    const fun = fn(); // fn() === outer === function outer() {} => fun
    fun(); // b = 9 // 相当于调用outer()函数
    
    const fun1 = fun();// fun() === outer() === f ===  function f() {} => fun1	// b = 9
    fun1(); // a = 1; b = 9	// 相当于调用f()函数
    
  • 闭包应用: 实现 数据 的 私有

    • 代码展示:
      // 闭包的应用:统计函数调用的次数
      function fun() {
        let i = 0;
        function fn() {
          i++;
          console.log(`fn()函数被调用了${i}`);
        }
        return fn;
      }
      const f = fun();  // fun() === fn === function fn() {} => f
      f();
      // Global -> fun() === fn === function fn() {} -> f ->  fn() -> i++ -> i
      // 可以查找到i,所以i不会被回收,调用完函数后,i应该被回收,现在没有回收,内存泄漏
      
  • ❗❗ 闭包面试:怎么理解闭包 -> 闭包的作用 -> 闭包可能引起的问题

1.6 ❌ 变量提升

  • 它允许在变量声声明之前既被访问(仅存在于var声明变量)
  • 变量提升: 在代码执行之前,检测在 当前作用域所有var 声明的变量,会将所有 用 var声明的变量 提升到 🔺当前作用域🔺最前面
  • 注意:
    • 变量提升出现在 相同作用域 当中
    • letconst 声明的变量 不存在变量提升
    • 只 提升 声明,不 提升 赋值
    • 代码展示:
      console.log(`${num}`);
      var num = 10;
      // 控制台打印: undefined件
      // 上面代码本质上就是下面的代码
      var num;
      console.log(`${num}`);
      num = 10;
      
  • ES6定义变量
    let : 变量
    const : 常量
    
    let/constvar 的区别
      + 预解析
        -> let/const 不会进行预解析(变量提升),必须先定义后使用
        -> var 会进行预解析
      + 变量名
        -> let/const 不能声明重复的变量名
        -> var 可以声明重复的变量名
      + 块级作用域(被代码块限制变量的使用范围)
        -> let/const 有块级作用域  (只要是能书写 {} 的代码块都能限制使用范围)
        -> var 没有块级作用域  (只有函数作用域才能限制使用范围)
    letconst 的区别
      + let 是 变量
        const 是 常量
      + let 在声明的时候可以不进行赋值 (留着以后用)
        const 在声明的时候必须进行赋值 (声明不赋值会报错)
      + let 声明的变量可以被修改                     
        const 声明的常量不可以修改 (修改就会报错)
    

二、函数进阶

2.1 函数提升

  • 代码执行之前,会把所有 函数声明 提升到 当前作用域最前面
  • 预解析 === 声明提升:在每个作用域的 代码执行之前,会把当前作用域代码中的声明部分提升到作用域最前面执行
    • 变量声明提升(var)
    • 函数声明提升(function)
    • ⚠ 注意:先提升var再提升function
  • ⚠🔺 注意:
    • 🔺 只 提升 函数声明,不 提升 函数调用
    • 🔺 函数表达式 必须 先声明 后调用(🔺不存在函数提升🔺)
    • 函数提升 优先级 高于 变量提升
  • 代码展示:
    fn();
    function fn() {
      console.log('函数提升,只提升声明,不提升调用');
    
    // 上面代码本质上就是下面代码
    function fn() {
      console.log('函数提升,只提升声明,不提升调用');
    }
    fn();
    ----------------------------------------------------------------------
    fun();
    var fun = function () { console.log('函数表达式不存在提升现象'); }
    // 报错:Uncaught TypeError(未捕获的类型错误): fun is not a function
    var fun;		// fun ->  
    fun();
    fun = function () { console.log('函数表达式不存在提升现象'); }
    // fun不能提升,
    // 上面代码是在给fun赋值,var声明的变量 只 提升声明,不 提升赋值
    

2.2 函数参数

  • 函数参数默认值 + 动态参数 + 剩余参数

2.2.1 动态参数

  • arguments: 函数内部 内置伪数组变量,包含了 调用 函数时 传入所有实参
  • 只能在普通函数里使用
  • 作用: 动态获取函数的实参
  • 注意:
    • 表示 所有实参集合
    • arguments 是一个 伪数组, 只存在于 函数内部
    • 箭头函数 🔺没有🔺 arguments
    • 🔺 可读写
  • 代码展示:
    // 动态参数 - arguments(伪数组)
    function getSum() {
      let sum = 0;
      for (let i = 0; i < arguments.length; i++) {
        sum += arguments[i];
      }
      return sum;
    }
    console.log(getSum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));	// 55
    

2.2.2 ✔ 剩余参数

  • 允许将一个 不定数量参数 表示为一个 数组
  • 是语法符号,置于 最末 函数形参 之前,用于获取 剩余(多于) 的实参
  • 注意:
    • 借助 获取的 剩余实参,是个真数组
    • 剩余参数放在 最末位
    • 如果 没有剩余的参数,得到的就是一个 空数组
  • 代码展示:
    // 剩余参数 - ...变量名(数组)
    function getSum(...arr) {
      console.log(arr);
    }
    getSum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);	
    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    ----------------------------------------------------------------------
    // ... 是语法符号,置于 最末 函数形参 之前,用于获取 剩余(多余) 的实参
    function getSum(a, b, c, ...arr) {
      console.log(arr);
    }
    getSum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);	
    // [4, 5, 6, 7, 8, 9, 10]
    
  • 动态参数剩余参数 的区别:
    • 动态参数伪数组
    • 剩余参数真数组
  • 拓展:
    • 展开运算符:
      • 展开运算符将一个 数组对象 展开
        • 不改变原始数组
      • 当你在 函数的实参 或者 数组 或者 对象 里面使用的时候是 展开运算符
      • 展开运算符写在数组前面
      • 应用场景:
        • 用于获取数组的最大值
        • 合并数组
        • 解构赋值(部分场景)
          • 解构赋值展开运算符 只能写在 最后一个元素前面
      • 展开运算符 or 剩余参数
      • 展开运算符: 数组 中使用,数组展开,在 数组内部 使用
      • 剩余参数: 函数参数 使用,得到真数组,在 函数内部 使用
      • 代码展示:
        // 获取数组最大值
        const arr = [3, 45, 2, 89, 54];
        console.log(...arr);	// 3 45 2 89 54
        console.log(Math.max(...arr));	// 89
        // 合并数组
        const arr1 = [1, 2, 4];
        const arr2 = [...arr, ...arr1];	// ...arr1 === 1, 2, 4
        console.log(arr2);	  // [3, 45, 2, 89, 54, 1, 2, 4]
        // 解构赋值
        const [n1, n2, ...n3] = [1, 2, 3, 4, 5];
        console.log(n1, n2, n3);	// 1 2 [3, 4, 5]
        ---------------------------------------------------------------
        const [n1, ...n2, n3] = [1, 2, 3, 4, 5];	// Uncaught SyntaxError: Rest element must be last element
        

2.3 ❗❗❗ 箭头函数

  • ⚠🔺 注意:
    • 箭头函数 没有 arguments,但是 剩余参数
    • 箭头函数 没有 this
    • 箭头函数 替代原本需要 匿名函数 的地方
    • 函数表达式简写方式 (匿名函数)
    • 声明式函数 不能写

2.3.1 基本语法

  • 语法:
    () => {}  // (function () {})
    () : 形参的位置
    => : 箭头函数的标志
    {} : 代码段
    
  • ⚠🔺 特性
    • 1️⃣ 只有 一个形参
      • 可以 省略小括号
      • 代码展示:⬇
    • 2️⃣ 代码段 只有 一行代码并且 返回值
      • 可以 省略 大括号并 自动 做为 返回值 被返回
      • 如果有返回值,可以 省略 return
      • 代码展示:⬇
    • 3️⃣ 加括号函数体 返回 对象字面量表达式(直接返回一个对象)
      • 箭头函数可以返回对象,但是必须用小括号包裹
      • 代码展示:⬇
    • 代码展示:
      // 箭头函数只有一个参数可以省略小括号
      const fn = x => { console.log(x); }
      fn(1);  // 1
       
      // 箭头函数没传递参数小括号还是要写的
      const fn1 = () => console.log('箭头函数没传递参数小括号还是要写的');
      fn1();	// '箭头函数没传递参数小括号还是要写的'
      
      // 箭头函数代码段只有一行代码,可以省略大括号
      const fn2 = x => console.log(x);
      fn2(2);  // 2
      
      // 箭头函数只有一行代码,且有返回值,可以省略return
      const fn3 = x => x + x;
      console.log(fn3(3));	// 6
      
      // 加括号的函数体返回对象字面量表达式(直接返回一个对象)
      // 对象的{} 和 函数的{}有冲突,需要拿个 () 包住对象
      const fn4 = uname => ({ uname: uname });
      console.log(fn4('迪迦奥特曼'));  // {uname: '迪迦奥特曼'}
      
      image-20220813174725057
  • 注意:
    • ⚠🔺箭头函数 属于 表达式函数,因此 不存在函数提升

2.3.2 箭头函数参数

  • 箭头函数 没有 arguments 动态参数,但是 剩余参数 …args
    • 代码展示:
      const getSum = (...args) => {
      	let sum = 0;
      	args.forEach(item => {
      		sum += item;
      	});
      	return sum;
      }
      console.log(getSum(3, 9));	// 12
      
  • 箭头函数只要你设置默认值,不管多少个形参,都必须要写小括号
    let fn = (a = 100) => { console.log(a); }
    fn();	    // 10
    fn(100);	// 100
    

2.3.3 箭头函数 this

  • 函数 才有this
  • 以前的this指向:
    // 以前this指向:谁调用这个函数 this 指向谁
    console.log(this);    // this -> window
    // window.console.log(this);
    
    // 普通函数的this
    function fn() {
    	console.log(this);
    }
    fn();   // this -> window
    // window.fn();
    
    // 对象方法里面的this
    const obj = {
    	uname: '迪迦奥特曼',
    	sayHi: function () {
    			console.log(this);
    	}
    }
    obj.sayHi();  // this -> obj
    
  • window 是JS中的全局对象,我们 声明 的 变量 或 函数 实际上是给 window 添加 属性 或 方法
  • 箭头函数 不会创建 自己的this,它只会 沿用 自己所在这条作用域链上一层作用域this
    • 箭头函数的 this指向被创建的时候上下文中的this(出生的时候,所在的作用域中的this是谁,以后就都指向谁)
  • 箭头函数里面的 this 任何方法都改变不了
    • 因为箭头函数没有 this (它用的是上一层作用域链的 this )
    • call / apply / bind 不能改变
  • 代码展示:
    // 箭头函数没有this指向,它只会 沿用 自己所在这条作用域链 的 上一层作用域 的 this
    const fn = () => console.log(this); // this -> window
    fn();	
    // 并不是window调用了fn,而是箭头函数没有this,它只会沿用自己所在这条作用域链的上一层作用域的this,上一层作用域是Global,Global的this指向window,所以指向window
    
    const obj = {
    	uname: '迪迦奥特曼',
    	sayHi: () => console.log(this)  // this -> window
    }
    obj.sayHi();  // window.obj.sayHi(); 
    // 箭头函数没有this,它只会沿用自己所在这条作用域链的上一层作用域的this
    // 也就是obj的this,window调用了obj,所以this指向window
    
    const obj1 = {
    	uname: '迪迦奥特曼',
    	sayHi: function () {
    		let i = 10;
    		const count = () => console.log(this);  // this -> obj1
    		count();
    	}
    }
    obj1.sayHi();
    // 箭头函数没有this,它只会沿用自己所在这条作用域链的上一层作用域的this
    // 既function的this,function(普通函数)的this指向函数的调用者obj1这个对象
    

三、❗❗ 解构赋值

3.1 数组解构

3.1.1 基本语法

  • 数组解构: 将数组的 单元值(数组元素) 快速 批量赋值 给 一系列变量 的 简介语法
  • 基本语法:
    • 赋值运算符 = 左侧[] 用于 批量 声明变量右侧数组单元值 将被 赋值给左侧的变量
    • 变量的顺序 对应 数组单元值的位置 依次 进行 赋值操作
  • ⚠🔺 注意: 数组解构 赋值 是按照 索引 赋值
  • 代码展示:
    // 将等号左侧数组里的单元值按顺序赋值给等号右侧的变量
    // 按照索引进行赋值
    const [a, b, c] = [1, 2, 3];
    console.log(a);		// 1
    console.log(b); 	// 2
    console.log(c); 	// 3
    
  • 利用数组解构交换两个变量的值
    let a = 11;
    let b = 22;
    [b, a] = [a, b];
    console.log(a)		// 22
    console.log(b) 	// 11
    ----------------------------------------------------------------------
    let a = 11
    let b = 22
    [b, a] = [a, b]
    console.log(a)
    console.log(b)
    // Uncaught ReferenceError: b is not defined
    // let b = 22 => 后面必须加分号才不会报错
    

3.1.2 特殊情况

  • 1️⃣ 变量多 单元值少
    • 右侧的单元值按顺序对左侧的变量进行赋值,没有赋值的变量则是 undefined
    • 代码展示:
      const [hr, lx, mi, fz, hw] = ['海尔', '联想', '小米', '方正'];
      console.log(hr, lx, mi, fz, hw);
      // 海尔 联想 小米 方正 undefined
      
  • 2️⃣ 变量少 单元值多
    • 右侧的单元值按顺序对左侧的变量进行赋值,剩余的单元值丢弃不用
    • 代码展示:
      const [hr, lx, mi] = ['海尔', '联想', '小米', '方正'];
      console.log(hr, lx, mi);
      // 海尔 联想 小米
      
  • 3️⃣ 利用 剩余参数 解决 变量少 的问题
    • 注意:
      • 剩余参数 返回的是一个真数组
      • 剩余参数 放在 最末位
    • 代码展示:
      const [hr, lx, ...mi] = ['海尔', '联想', '小米', '方正'];
      console.log(hr, lx, mi);
      // 海尔 联想 [小米, 方正]
      
  • 4️⃣ 防止 undefined 传递
    • 设置默认值
    • 代码展示:
      const [hr, lx, mi, fz, hw = '华为'] = ['海尔', '联想', '小米', '方正'];
      console.log(hr, lx, mi, fz, hw);  // 海尔 联想 小米 方正 华为
      
  • 5️⃣ 按需导入,忽略某些值
    • 变量可以忽略,但是位置还是要留的
    • 代码展示:
      const [hr, lx, mi, , hw = '华为'] = ['海尔', '联想', '小米', '方正'];
      console.log(hr, lx, mi, hw);  // 海尔 联想 小米 华为
      
  • 6️⃣ 多维数组解构
    • 代码展示:
      const [a, [b, [c, d, [e]]], f] = [1, [2, [3, 4, [5]]], 6];
      console.log(a, b, c, d, e, f);
      // 1 2 3 4 5 6
      

3.2 对象解构

  • 对象解构: 将对象 属性 和 方法 快速 批量赋值 给一些列 变量 简介语法

3.2.1 基本语法

  • 赋值运算符 = 左侧{} 用于 批量声明变量右侧对象的属性值将被 赋值 给左侧的变量
  • 对象属性的值 将被赋值给 与 对象属性名 相同变量
  • 注意:
    • ⚠🔺 对象解构赋值的时候按照 属性名 进行 赋值
    • 对象解构的变量名不要和外面的变量名冲突否则报错
    • ⚠🔺 对象中 找不到变量名一致的属性名变量值undefined
    • ⚠🔺🔺 变量名对象的属性名 必须一致否则 变量的值 就是 undefined
    • ⛔错误展示:
      const { uname, Age } = { uname: '迪迦奥特曼', age: 22 };
      console.log(uname, Age);	// 迪迦奥特曼 undefined
      
  • 代码展示:
    const {uname, age} = {uname: '迪迦奥特曼', age: 22};
    console.log(uname, age);	// 迪迦奥特曼 22
    // 等价于
    // const uname = obj.uname;
    // const age = obj.age;
    

3.2.2 特殊情况

  • 1️⃣ 对象解构的变量名 可以重新声明
    • 语法: 旧变量名: 新变量名
    • 代码展示:
      const uname = '赛罗奥特曼';
      const { uname: username, age } = { uname: '迪迦奥特曼', age: 22 };
      console.log(username, age);	// 迪迦奥特曼 22
      
  • 2️⃣ 解构数组对象
    • 代码展示:
      const obj = [
        {
          uname: '迪迦奥特曼',
          age: 22
        },
        {
          uname: '赛罗奥特曼',
          age: 23
        }
      ];
      const [{ uname, age }, { uname: userName, age: userAge }] = obj;
      console.log(uname, age, userName, userAge); // 迪迦奥特曼 22 赛罗奥特曼 23
      
  • 3️⃣ 多级对象解构
    • 解构的时候必须写 🔺对象名🔺,不能打印
    • 代码展示:
      const pig = {
        name: '佩奇',
        family: {
          mother: '猪妈妈',
          father: '朱爸爸',
          sister: '乔治'
        },
        age: 6
      };
      const { name, family: { mother, father, sister }, age } = pig;
      console.log(name, mother, father, sister, age); // 佩奇 猪妈妈 朱爸爸 乔治 6 
      
      const person = [
        {
          name: '佩奇',
          family: {
            mother: '猪妈妈',
            father: '朱爸爸',
            sister: '乔治'
          },
          age: 6
        }
      ];
      const [{ name, family: { mother, father, sister }, age }] = person;
      console.log(name, mother, father, sister, age); // 佩奇 猪妈妈 朱爸爸 乔治 6
      
      ✔✔
      // 1. 这是后台传递过来的数据
      const msg = {
        "code": 200,
        "msg": "获取新闻列表成功",
        "data": [
          {
            "id": 1,
            "title": "5G商用自己,三大运用商收入下降",
            "count": 58
          },
          {
            "id": 2,
            "title": "国际媒体头条速览",
            "count": 56
          },
          {
            "id": 3,
            "title": "乌克兰和俄罗斯持续冲突",
            "count": 1669
          },
        ]
      };
      
      // 需求1:请将以上msg对象,采用对象解构的方式,只选出 data 方便后面使用渲染页面
      const { data } = msg;
      console.log(data);
      // 需求2:上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
      // const { data } = msg;
      // msg 虽然很多属性,但是我们利用解构只要 data值
      function render({ data }) {
        // const { data } = arr;
        // 我们只要 data 数据
        // 内部处理
        console.log(data);
      }
      render(msg);
      
      // 需求3:为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
      function render({ data: myData }) {
        // 要求将 获取过来的 data数据 更名为 myData
        // 内部处理
        console.log(myData);
      }
      render(msg);
      
  • 今日案例拓展:
    • :active 伪类选择器
      • 活动链接(点击的时候就是活动链接)
    • draggable 可拖拽的
      • draggable ="false" 禁止拖拽
      • draggable ="true" 可以拖拽

四、对象进阶

4.1 创建对象的三种方式

  • 1️⃣ 字面量 创建
    • const obj = {};
      
  • 2️⃣❌ new Object 创建 (系统的构造函数)
    • const obj = new Object();
      
    • 追加属性
      const obj = new Object({uname: '迪迦奥特曼', age: 22});
      
  • 3️⃣ 工厂函数创建
    • image-20220822085923461
  • 4️⃣ 构造函数 创建 (自定义的构造函数)
    • image-20220822090653886
  • new操作符的功能
    • 在函数代码执行之前,隐式的创建一个空对象,把this指向空对象
    • 执行函数代码
    • 在函数代码执行之后,隐式的返回this
  • 操作对象
    • 1️⃣ 点语法 — 操作对象
      // 增 
      o.name = 'Jack'
      o.age = 20
      // 删
      delete o.name
      // 查
      console.log(o.age)
      // 改
      o.age = 22
      
  • 2️⃣ 数组关联语法 — 操作对象
    增
    o['name'] = 'Jack'
    o['age'] = 20
    o['gender'] = '男'
    o['class'] = 1delete o['name']
    查
    o['age']
    改
    o['age'] = 21
    
  • 判断一个成员在不在对象里面 — in
    • 语法:
      属性名 in '对象名'	
      
    • 返回值: true / false
    • 代码展示:
      let obj = {
        name: 'jack',
        age: 18,
        gender: '男',
        score: 100
      };
      console.log('name' in obj);	// true
      

4.2 构造函数(自封装)

  • 是一种 特殊的函数,主要用来 初始化对象
  • 可以用构造函数 创建多个 类似 对象(对公共的部分进行抽取并封装)
  • 规范:
    • 命名以 大写字母 开头
    • ⚠🔺 只能由 new 操作符来执行
  • ⚠🔺 注意:
    • 使用 new 关键字调用函数的行为被称为 实例化
    • 构造函数内部 无需写return返回值 即为 新创建的对象
    • 构造函数内部的 return 返回的值 无效,所以不要写 return
  • ❗❗ 实例化执行的过程: (面试)
    • 1️⃣ 创建新的 空对象
    • 2️⃣ 构造函数 this 指向 新对象
    • 3️⃣ 执行构造函数代码,修改this,添加新属性
    • 4️⃣ 返回新对象
  • 代码展示:
    image-20220815113826581
    image-20220815113843724

4.3 实例成员 & 静态成员

4.3.1 实例成员

  • 构造函数 创建的 对象 称为 实例对象
  • 🔺 实例对象属性方法 称为 实例成员
  • 注意:
    • 为 构造函数 传入 参数,动态创建 结构相同值不同 的 对象
    • 构造函数 创建 的 实例对象 彼此独立 互不影响

4.3.2 静态成员

  • 🔺 构造函数属性方法 被称为 静态成员
  • ⚠🔺 静态成员方法 中的 this 指向 构造函数
  • 一般 公共特征 的属性或方法 静态成员设置为静态成员
    image-20220815195529890

五、内置构造函数

  • 字符串、数值、布尔等基本数据类型也都有专门的构造函数,称为 基本包装类型
  • 引用类型: Object、Array、RegExp、Date 等
  • 包装类型: String、Number、Boolean 等

5.1 Object

  • ❌ 使用内置构造函数创建对象
    const obj = new Object({uname: '迪迦', age: 22});
    
  • 常用的 静态方法
    • 静态方法: 只有 构造函数 Object 才可以 调用 (写在构造函数身上的方法)
    • 1️⃣ ✔ Object.keys()
      • 获取当前对象中的 所有 可枚举的 属性名(键)
      • 语法:
        Object.keys(对象名)
        
      • 返回值: 数组 (数组的元素 = 对象的属性)
      • 代码展示:⬇
    • 2️⃣ ✔Object.values()
      • 获取对象中 所有属性值
      • 语法:
        Object.values(对象名)
        
      • 返回值: 数组(数组的元素 = 对象属性值)
      • 代码展示:⬇
    • 3️⃣ Object.assign()
      • 用于 对象拷贝(浅拷贝)
      • 使用场景: 给对象添加属性
      • 语法:
        Object.assign(b, a)	// 把 a 拷贝给 b
        Object.assign(拷贝者, 被拷贝者);
        
      • 代码展示:⬇
    • 代码展示:
      // 静态方法:只有 构造函数 Object 才可以调用
      const obj = { uname: '迪迦', age: 22 };
      // Object.keys(对象):获取对象的 所有属性名,放在一个数组里面返回
      const arr = Object.keys(obj);
      console.log(arr);   // ['uname', 'age']
      
      // Object.values(对象名):获取对象的 所有属性值,放在一个新数组中返回
      const arr1 = Object.values(obj);
      console.log(arr1);  // ['迪迦', 22]
      
      // Object.assign(拷贝者, 被拷贝者):将对象a拷贝给对象b
      const obj1 = {};
      Object.assign(obj1, obj);
      console.log(obj1);  // { uname: '迪迦', age: 22 }
      // 追加属性
      Object.assign(obj1, {gender: '男'});
      console.log(obj1);	// {uname: '迪迦', age: 22, gender: '男'}
      

5.2 Array

  • ❌使用Array内置构造函数创建数组
    const arr = new Array();							// 创建一个空数组
    const arr = new Array(5);							// 一个参数:数组的长度
    const arr = new Array(1, 2, 3, 4, 5); // 多个参数:[1, 2, 3, 4, 5]
    

5.1 数组常用方法总结

5.1.1 改变原始数组 (7)
  • 1️⃣ push()
    • 描述:一个或多个元素 追加到 数组的末尾 ,并返回 追加元素之后 数组的 length
    • 语法:
      arr.push(elmeent1, ..., elementN);
      
    • 返回值:
      • 追加元素 之后 数组的 length
    • 代码展示:
      const arr = [1, 2, 3];
      const length = arr.push(4, 5);	// 追加元素之后arr的length
      console.log(arr);			// [1, 2, 3, 4, 5]
      console.log(length);	// 5
      
  • 2️⃣ unshift()
    • 描述:一个或多个元素 插入到 数组的开头 ,并返回 插入元素之后 数组的 length
    • 语法:
      arr.unshift(element1, ..., elementN);
      
    • 返回值:
      • 插入元素 之后 数组的 length
    • 代码展示:
      const arr = [3, 4, 5];
      const length = arr.unshift(1, 2);	// 插入元素之后arr的length
      console.log(arr);			// [1, 2, 3, 4, 5]
      console.log(length);	// 5
      
  • 3️⃣ pop()
    • 描述: 删除 数组 最后一个元素,并返回 被删除的元素
      • ⚠ 会 改变 数组的 length
    • 语法:
      arr.pop();
      
    • 返回值:
      • 被删除的元素
    • 代码展示:
      const arr = [1, 2, 3];
      const lastValue = arr.pop();	// 被删除的元素
      console.log(arr);			// [1, 2]
      console.log(lastValue);	// 3
      
  • 4️⃣ shift()
    • 描述: 从数组中删除 第一个元素,并返回 该元素的值
      • 改变数组的长度
    • 语法:
      arr.shift()
      
    • 返回值: 被删除的元素
    • 代码展示:
      const arr = [1, 2, 3];
      const firstValue = arr.shift();	// 被删除的元素
      console.log(arr);			// [2, 3]
      console.log(firstValue);	// 1
      
  • 5️⃣ splice()
    • 描述: 通过 删除替换 现有元素原地添加新元素修改数组,并以 数组的形式返回被修改的内容
    • 语法:
      array.splice(start[, deleteCount[, item1][, ...])
      
    • 返回值:数组的形式 被修改 的 内容
    • 注意:
      • 删除/替换 的时候,包括 开始索引位置
      • 添加 的时候,是添加在 开始索引位置之前
        • 添加 的时候返回的是 空数组
    • 代码展示:
      // 删除
      const arr = [1, 2, 3];
      const Value = arr.splice(1, 1);	// 以数组的形式返回被修改的内容
      console.log(arr);			// [1, 3]
      console.log(Value);		// [2]
      // 替换
      const arr = [1, 2, 3];
      const Value = arr.splice(1, 1, 6);	// 以数组的形式返回被修改的内容
      console.log(arr);			// [1, 6, 3]
      console.log(Value);		// [2]
      // 添加
      const arr = [1, 2, 3];
      const Value = arr.splice(1, 0, 8);	// 
      console.log(arr);			// [1, 8, 2, 3]
      console.log(Value);		// []
      
  • 6️⃣ sort() - 数组排序
    • 描述: 对数组元素进行排序,并返回数组
    • 语法:
      arr.sort()	可以省略参数,升序
      arr.sort(function (a, b) { return a - b } )		升序排列
      arr.sort(function (a, b) { return b - a } )		降序排列
      
    • 返回值: 排序好的数组
    • 代码展示:
      const arr = [23, 45, 78, 10];
      arr.sort();
      console.log(arr);	// [10, 23, 45, 78]
      arr.sort(function (a, b) {return a - b});
      console.log(arr);	// [10, 23, 45, 78]
      arr.sort(function (a, b) {return b - a});
      console.log(arr);	// [78, 45, 23, 10]
      
  • 7️⃣ reverse() - 反转数组
    • 描述: 将数组中的元素的 位置颠倒 ,并返回该数组
    • 语法:
      arr.reverse();
      
    • 返回值: 颠倒顺序后的数组
    • 代码展示:
      const arr = [1, 2, 3, 4, 5];
      arr.reverse();
      console.log(arr);	// [5, 4, 3, 2, 1]
      
5.1.2 不改变原始数组
  • 1️⃣ ✔ forEach - 遍历数组
    • 描述: 适合遍历 数组对象
    • 语法:
      Array.forEach(function (item[, index[, Array]]) {  } )
      
    • 返回值:
    • 代码展示:
      const arr = ['red', 'purple', 'pink'];
      arr.forEach((item, index) => {
        console.log(item);
        console.log(index);
      });
      // red 0 purple 1 pink 2
      
  • 2️⃣ ✔ map() - 迭代数组(映射数组)
    • 描述: 遍历原数组,把原数组中的每一个数据加工改造,形成一个新数组返回
    • image-20220815114702965
    • 语法:
      Array.map(function (item[, index[, Array]]) {} )
      
    • 返回值: 新数组
      • 将原始数组里面的元素进行处理之后添加到新数组里
      • 新数组length = 旧数组length
    • 代码展示:
      const arr = ['red', 'blue', 'green'];
      const newArr = arr.map(function (item, index, arr) {
        return item + '老师';
      });
      console.lgo(newArr);		// ['red老师', 'blue老师', 'green老师']
      
      const arr1 = [10, 20, 30];
      const newArr1 = arr1.map(function (item, index, arr) {
        return item + 10;
      });
      console.log(newArr1);		// [20, 30, 40]
      
  • 3️⃣ ✔ filter() - 筛选数组
    • 描述: 过滤 原始数组中的数据,把 满足条件 的数据放在一个新数组中
      • 在执行函数返回true的情况下,留下该数据,返回为false去除该数据
    • image-20220815114354741
    • 语法:
      Array.filter(function (item[, index[, Array]]) {} )
      
    • 返回值: 是一个新数组,里面是所有原始数组中满足条件的数据
    • 代码展示:
      const arr = [20, 1, 34, 60, 49, 90, 200, 288];
      const newArr = arr.filter(item => {
        return item > 100;
      });
      console.log(newArr); // [200, 288]
      -------------------------------------------------------------------
      const arr = [23, 4, 19, 22, 56, 77];
      const newArr = arr.filter(item => {
      	if (item % 2 === 0) {
      		return true;
        } else {
          return false;
        }
      });
      console.log(newArr);
      // 上面代码等价于下面代码
      const newArr = arr.filter(item => item % 2 === 0);
      console.log(newArr);
      
  • 4️⃣ ✔ reduce() - 累计器(求和)
    • 描述: 每一次运行 reduce 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值
    • image-20220815210033254
    • 语法:
      Array.reduce(function () {}, 起始值)
      Array.reduce(function (累计值, 当前元素[, 索引号][, 原数组]) {}[, 起始值])
      
    • 参数:
      • 起始值可以省略
        • 如果写就作为第一次累计的起始值
        • 如果没有,就取第一个元素作为起始值(累加的时候从第二个元素开始)
      • 有起始值,则以起始值为准开始累计,累计值 = 起始值
      • 没有起始值,则累计值以数组的第一个元素作为起始值开始累计
      • 后面每次遍历就会用后面的数组元素 累计 到 累计值 里面
    • 返回值: 返回函数累计的处理的结果
    • 代码展示:
      // 数组.reduce(function (累计值, 当前元素) {}, 起始值)
      const arr = [1, 2, 3, 4, 5];
      const sum = arr.reduce((prev, item) => prev + item, 0);
      // prev + item
      // 0 + 1 = 1
      // 1 + 2 = 3
      // 3 + 3 = 6
      // 6 + 4 = 10
      // 10 + 5 = 15
      console.log(sum);		// 15
      const sum = arr.reduce((prev, item) => prev + item);
      // 将数组的第一个元素给prev,从第二个元素开始累加
      // prev + item
      // 1 + 2 = 3
      // 3 + 3 = 6
      // 6 + 4 = 10
      // 10 + 5 = 15
      console.log(sum);		// 15
      
  • 5️⃣ join()
    • 描述: 将一个 数组类数组对象 的所有元素连接成一个 字符串,并返回这个字符串
      • 注意:所有的元素 转换成 字符串分隔符 将这些字符串 连接 起来
    • 语法:
      arr.join([分隔符]);
      
      • 参数: 默认 使用 逗号
    • 返回值: 字符串
    • 代码展示:
      const arr = ['迪迦', '赛罗', '泰罗'];
      let str = arr.join();
      let str1 = arr.join('-');
      console.log(str);		// 迪迦,塞罗,泰罗
      console.log(str1);	// 迪迦-塞罗-泰罗
      console.log(arr);		// ['迪迦', '赛罗', '泰罗']
      
  • 6️⃣ ✔find() - 返回数组中满足条件的 第一个元素
    • 描述: 返回 数组 中 满足条件第一个元素
    • image-20220816155818235
    • 语法:
      Array.find(function (item, index, arr) {} [, thisArg])
      
    • 返回值:
      • 有 满足条件 元素: 返回 第一个 元素
      • 没有 这个元素: undefined
    • 代码展示:
      const arr = [5, 12, 8, 130, 44];
      const found = arr.find(item => item > 10);
      console.log(found);		// 12
      
  • 7️⃣ some()
    • 描述: 判断 数组中是不是 至少有一个元素 满足条件
    • image-20220816155750249
    • 语法:
      Array.some(function (item, index[, arr]) {})
      
    • 返回值: 布尔值(true / false)
      • 有 一个 满足 - true
      • 都不 满足 - false
    • 代码展示:
      const arr = [1, 2, 3, 4, 5];
      const value = arr.some(item => item >= 3);
      console.log(value);		// true
      
  • 8️⃣ ✔every()
    • 描述: 判断 数组 里面的 每一个元素 是不是都 满足条件
    • image-20220816155732177
    • 语法:
      Array.every(function (item, index[, arr]) {})
      
    • 返回值: 布尔值(true / false)
      • 都满足 - true
      • 有 一个 不满足 - false
    • 代码展示:
      const arr = [2, 26, 56, 34, 67];
      let flag = arr.every(item => item > 30);
      console.log(flag);	// false
      
  • 9️⃣ concat() - 合并数组
    • 描述: 合并 两个或多个数组。此方法不会更改现有数组,而是返回一个 新数组
    • 语法:
      const new_array = oldArr.concat(value1[, value2[, ...[, valueN]]])
      
    • 参数:
      • 将参数按顺序追加到数组末尾
    • 返回值: 合并之后新数组
    • 代码展示:
      // 都是数组
      const arr = [1, 2, 3];
      const arr1 = [4, 5, 6];
      const new_array = arr.concat(arr1);
      console.log(new_array);	// [1, 2, 3, 4, 5, 6]
      // 参数不是数组
      const num = 8;
      const new_array = arr.concat(num);
      console.log(new_array);	// [1, 2, 3, 8]
      // 参数是对象
      const obj = {uname: '奥特曼', age: 22};
      const new_array = arr.concat(obj);
      console.log(new_array);	// [1, 2, 3, { uname: "奥特曼", age: 22 }]
      
  • 🔟 slice() - 提取元素
    • 描述: 获取指定的元素
    • 语法:
      Array.slice(开始索引, 结束索引)	
      
      • 参数:
        • 包前不包后
        • 第一个参数不写: 头 ➡ 指定位置
        • 第二个参数不写: 指定位置 ➡ 尾
        • 参数可以是一个负数 -> length + 负数
    • 返回值: 一个含有被提取元素的 新数组
    • 代码展示:
      const arr = [1, 2, 3, 4, 5, 6];
      const newArr = arr.slice(2, 4);		// [3, 4]
      // 第一个参数不写
      const newArr = arr.slice(4);			//  [5, 6]
      // 第二个参数不写
      const newArr = arr.slice(2);			// [3, 4, 5, 6]
      // 参数是负数
      const newArr = arr.slice(2, -2);	//  [3, 4]
      // 不写参数
      const newArr = arr.slice();				// [1, 2, 3, 4, 5, 6]
      console.log(newArr);	
      
  • 数组降维(数组扁平化)
    arr.flat(几维数组);
    arr.flat(Infinity);		// 一劳永逸,不管几维数组都能降到一维数组
    
  • 正向 查看数组里面 指定这个 数据的索引
    Array.indexOf(数据)
    Array.indexOf(数据, 开始索引)	从哪个索引开始向后查找
    返回值: 如果有这个数据,是第一个满足条件的数据的索引
    		如果没有这个数据,就是 -1
    
  • 反向 查看数组里面 指定数据 的索引
    Array.lastIndexOf(数据)
    Array.lastIndexOf(数据, 开始索引)
    注意: 虽然是反向查找,但是索引还是正常索引
    
  • 使用 数组里面的内容 替换 数组里面的内容
    Array.copyWithin(目标位置, 开始索引, 结束索引)
    	->目标位置:当你替换内容的时候,从哪一个索引位置开始替换
        ->开始索引:数组哪一个索引位置开始当作替换内容,默认是 0 
        ->结束索引:数组哪一个索引位置结束当作替换内容,默认是 结尾
    返回值: 是一个新数组(替换后的数组)
    
  • 使用 指定数据区 填充 数组
    • image-20220816155906720
    Array.fill(要填充的数据, 开始索引, 结束索引)
    前提: 数组要有 length
    返回值: 填充好的数组
    
  • 查看 数组中是不是有 某一个数据
    Array.includes(数据)
    返回值: 一个布尔值
          有这个数据就是 true
          没有这个数据就是 false
    
  • 根据条件找到数组里面满足条件的数据的索引
    • image-20220816155843416
    Array.findIndex(function (item) {} )
    返回值: 找到满足条件的第一个元素的索引
    
image-20220815210652899

5.2 伪数组 ➡ 真数组

  • 静态方法:
    • 语法:
      Array.from()
      
    • 代码展示:
    // Array.from(伪数组):将伪数组转换为真数组
    // 返回值:真数组
    const arr = Array.from(document.querySelectorAll('li'));
    arr.pop();
    console.log(arr);
    
    image-20220815234811144
  • 伪数组(对象)
    • 有索引值
    • 有length属性
    • 索引值 并且 有 length属性对象 就是 伪数组
    • const obj = {
        0: 'zs',
        1: 20,
        2: '小妹',
        length: 3
      }
      console.log(Array.from(obj));	// ['zs', 20, '小妹']
      

5.3 String

  • ❌ 字符串的创建
    字面量创建 let str = 'hello world'
    内置构造函数创建 let str = new String('hello world')
    

5.3.1 字符串方法

  • 都是 实例方法
  • image-20220816160128364
  • 注意: 所有字符串方法不会改变 原始字符串
  • 1️⃣ ✔ substring() - 截取字符串
    • 语法:
      str.substring(indexStart[, indexEnd])
      
    • 参数:
      • indexStart = indexEnd ➡ 返回一个 空字符串
      • 省略indexEnd ➡ 提取字符 一直字符串末尾
      • 如果任一 参数小于0或为NaN,则被 当作 0
      • 如果任一 参数大与 string Name.length,则被当作 stringName.length
      • 如果 indexStart 大与 indexEnd,则substring 的执行结果就像两个参数调换了一样。
    • 返回值: 指定部分 的 新字符串 (截取的部分)
    • 注意: 包前 不包后
    • 代码展示:
      // 截取字符串
      // str.substring(indexStart[, indexEnd]);
      // 返回截取的部分
      const str = '迪迦奥特曼,塞罗奥特曼,银河奥特曼,艾克斯奥特曼';
      // 正常索引值(包前不包后)
      const newStr = str.substring(4, 9); // 曼,塞罗奥
      // 一个参数
      const newStr = str.substring(6);  // 塞罗奥特曼,银河奥特曼,艾克斯奥特曼
      // indexStart = indexEnd
      const newStr = str.substring(6, 6); // 空字符串
      // 参数小于0或者为NaN
      const newStr = str.substring(-9); // 迪迦奥特曼,塞罗奥特曼,银河奥特曼,艾克斯奥特曼
      const newStr = str.substring(NaN); // 迪迦奥特曼,塞罗奥特曼,银河奥特曼,艾克斯奥特曼
      // 参数大与stringName.length
      const newStr = str.substring(100);	// 空字符串
      // indexStart>indexEnd
      const newStr = str.substring(9, 4); // 曼,塞罗奥
      console.log(newStr);
      
    • ❌ 拓展:也是截取字符串的方法
      • substr()
      • slice()(弃用)
  • 2️⃣ ✔ split() - 将 字符串 拆分成 数组 (分割)
    • 字符串中截取从开始下标到的指定数目的字符
    • 语法:
      str.split(分隔符)
      
    • 参数:
      • 字符串里面用的什么分隔符参数就是什么
      • 没有参数 / 别的字符: 数组里面的 元素 就是 字符串本身
      • 空字符串: 将字符串的每一个 字母数组文字 或 标点符号 都作为一个 数组元素
    • 返回值: 数组
    • 注意: 和 数组的join方法 相反
    • 代码展示:
      // String.split():将字符串转换成数组
      // 字符串用什么分隔参数就是什么
      const str = '迪迦奥特曼,塞罗奥特曼,银河奥特曼,艾克斯奥特曼';
      const arr = str.split(',');
      console.log(arr); // ['迪迦奥特曼', '塞罗奥特曼', '银河奥特曼', '艾克斯奥特曼']
      const str1 = '2022-8-16';
      const arr1 = str1.split('-');
      console.log(arr1);  // ['2022', '8', '16']
      
    • 3️⃣ ✔ startsWith() - 检测是否以某个(某段)字符开头
      • 语法:
        string.startsWith(检测字符串[, 检测位置索引号])
        
      • 参数:
        • 检测索引位置:默认 是以 0 开始
      • 返回值: 布尔值(true / false)
      • 注意:
        • 包前不包后
      • 代码展示:
        // 检测是否以某个(某段)字符开头
        // str.startsWith(检测字符串[, 检测索引的位置])
        // 返回值:true / false
        const str = '迪迦奥特曼,赛罗奥特曼,银河奥特曼,艾克斯奥特曼';
        // const flag = str.startsWith('迪迦奥特曼'); // true
        const flag = str.startsWith('赛罗奥特曼', 6); // true
        console.log(flag);
        
  • 4️⃣ ✔ includes() - 判断一个字符串是否包含在另一个字符串中(返回true / false)(includes - 包含)
    • 语法:
      str.includes(搜索的字符串[, 检测索引位置])
      
    • 返回值: true / false
    • 代码展示:
      // .str.includes(搜索的字符串[, 检测位置的索引]):判断一个字符串是否包含在另一个字符串中
      // 返回值:true / false
      const str = '迪迦奥特曼,赛罗奥特曼,银河奥特曼,艾克斯奥特曼';
      const flag = str.includes('赛罗奥特曼');
      console.log(flag);
      
  • 5️⃣ replace() - 替换字符串中指定的字符
    • 语法:
      字符串.replace(要替换的旧字符, 替换的新字符)
      
    • 返回值: 替换之后 的字符串
    • 注意:
      • 不改变 原始字符串
      • 默认 只替换 匹配到的 第一个字符串
      • 可以 配合 正则表达式(g 和 i) 来 全局匹配 并且 不区分大小写
    • 代码展示:
      let str = '赛文奥特曼真好看,并且赛文奥特曼真厉害!';
      const newStr = str.replace(/赛文/g, '迪迦');
      console.log(newStr);	// 迪迦奥特曼真好看,并且迪迦奥特曼真厉害!
      
image-20220822172410240

5.4 Number

  • Number内置构造函数,用于创建数值
  • toFixed(保留几位小数) - 保留小数位的长度
  • 具有 四舍五入 功能
  • ⚠🔻 使用该方法之后数字会转换为 字符串
    const price = 12.345;
    const price1 = 12.921;
    // 没有参数
    price.toFixed();	// 12	(string)
    price1.toFixed();	// 13	(string)
    // 有参数
    price.toFixed(2);	// 12.35	(string)
    price1.toFixed(2);	// 12.92	(string)
    

六、编程思想

6.1 面向过程变成

  • 面向过程: 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了
  • 优点:
    • 性能 比面向对象 ,适合跟硬件联系很紧密的东西,例如单片机
  • 缺点:
    • 没有面向对象易维护、易复用、易扩展

6.2 面向对象编程 (oop)

  • 面向对象: 是把事务分解成为一个个对象,然后由对象之间分工与合作。
    • 面向对象是以 功能 来划分问题的
  • 特性:
    • 封装性
    • 继承性
    • 多态性
  • 优点:
    • 易维护、易复用、易扩展,基于面向对象封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
  • 缺点:
    • 性能比面向过程低

七、构造函数

  • 构造函数 体现了 面向对象封装特性
  • 构造函数 实例创建 的 对象 彼此独立、互不影响
  • 构造函数存在的问题:浪费内存(可以用原型解决)
  • 代码展示:
    // 构造函数:公共的属性和方法封装到构造函数里面
    function Star(uname, age, gender) {
      this.uname = uname;
      this.age = age;
      this.gender = gender;
      this.sing = function () { console.log('构造函数'); }
    }
    const dj = new Star('迪迦', 22, '男');
    const sl = new Star('赛罗', 23, '女');
    // 通过构造函数创建的对象彼此独立,互不影响
    console.log(dj === sl); // false
    console.log(dj.sing === sl.sing); // false
    // dj 和 sl 的地址不同,所指向的function也不同
    
    image-20220817143524608

八、❗❗❗ 原型

8.1 原型

  • 原型
    • 是一个 对象,我们称 prototype原型对象
    • 跟随函数一起创建的对象
  • 构造函数 通过 原型 分配的 方法 是 所有对象 所 共享的
  • js规定,每一个 构造函数 都有一个 prototype 属性,指向 另一个对象,所以我们称为 原型对象
  • 这个原型对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
    • 可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法
  • 作用: 为当前函数创建的实例添加公共的属性和方法(把那些不变的方法挂载在prototype上)
  • ⚠🔺 构造函数原型对象 中的 this 都指向 实例对象
    • 原型上的方法,谁调用就指向谁(实例对象 + 原型自己)
  • 有函数 就有 原型对象
  • 代码展示:
    // 公共的属性写到 构造函数 里面
    function Star(uname, age, gender) {
      this.uname = uname;
      this.age = age;
      this.gender = gender;
    }
    const Dj = new Star('迪迦', 22, '男');
    const Sl = new Star('赛罗', 23, '女');
    // 每个构造函数都有一个prototype(原型)属性,
    // console.dir(Star.prototype);
    // 公共的方法写在原型对象上
    Star.prototype.sing = function () { console.log('原型-prototype'); }
    console.log(Dj.sing === Sl.sing); // true
    // 原型也可以调用自己身上的方法
    Star.prototype.sing(); // this -> Star.prototype
    
    image-20220817144414694.png
    // 封装一个求数组最值和和的方法(只要是数字数组就可以使用)
    const arr = [6, 3, 8, 2, 9, 0];
    // const arr = new Array(6, 3, 8, 2, 9);
    Array.prototypeGgetValue = function () {
      return [Math.max(...this), Math.min(...this), this.reduce((prev, item) => prev + item)]
    }
    console.log(arr.GetValue());	// [9, 0, 28]
    

8.2 constructor属性

  • 每个 原型对象 里面都有 constructor 属性(constructor ➡ 构造函数)
  • 作用: 该属性 指向 该原型对象构造函数
    • 简单理解:指向我的爸爸,我是个有爸爸的孩子
  • ⚠🔺 每个 构造函数 都有 prototype属性 ,每个 prototype属性 都有 constructor属性
    • image-20220817174753040
    • image-20220817174835531
  • 使用场景:
    • 如果有 多个对象方法,我们可以 原型对象 采用 对象形式 赋值单个方法是追加
    • 但是这样就会 覆盖 构造函数 原型对象 原来内容,这样 修改后原型对象constructor属性不再 指向 当前 构造函数
    • 此时,我们可以在 修改后原型对象 中,添加一个 constructor属性 指向 原来的构造函数
    • 代码展示:
      • 原来的prototype:image-20220817203130693
      • image-20220817202253946

8.3 对象原型

  • 对象 都会有一个 __proto__ 指向 构造函数prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在
    • 注意:
      • __proto__ 是一个 🔺只读属性🔺 (前后两个杠)
      • Chrome 中 [[prototype]] 和 __proto__ 意义相同
      • 用来表示当前实例对象指向哪个原型对象prototype
      • __proto__ 对象原型 里面也有一个 constructor属性指向 创建 该实例对象 的 构造函数
    • image-20220817212939553
    • image-20220817213451767
  • ⚠🔺🔺 注意: (混淆点)
    • 每个 构造函数 都有 prototype(原型对象)
    • 每个 prototype(原型对象) 都有 constructor,指向 该原型对象构造函数
    • 每个 对象 都有 __proto__(对象原型)(属性),指向 构造函数原型对象
    • 每个 对象原型 都有 constructor 属性,指向 创建 该实例构造函数
      image-20220817233131154
  • 对象都会有一个属性 __proto__ 指向构造函数的prototype原型对象,之所以对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有___proto__
  • 🔺🔺⚠⚠总结:
    • 1️⃣ prototype 是什么?哪里来的
      • 原型(原型对象)
      • 构造函数 都 自动有 原型
    • 2️⃣ constructor属性 在哪里?作用是啥?
      • prototype原型对象原型 __proto__ 里面 都有
      • 都 ➡ 创建 原型 / 实例对象构造函数
    • 3️⃣ __proto__ 属性在哪里?指向谁?
      • 实例对象 里面
      • 原型对象 prototype

8.4 原型继承

  • JS中大多是 借助 原型对象 实现 继承的特性
    • 父构造函数(父类)
    • 子构造函数(子类)
    • 子类的原型 = new 父类
  • 使用构造函数实现继承:
    • 类:创建一类对象的模板
    • 原型链继承:利用原型链的关系,实现继承的效果
      • 在ES6之前,没有继承的语法,可以使用call和原型链来完成继承的效果,子构造函数继承父构造函数
      • 1、子构造函数 创建的实例 拥有 父构造函数 创建的实例 相同 属性结构(call借用父构造函数的代码,修改其中this指向为子构造函数的实例)
      • 2、子构造函数 创建的实例 可以调用 父构造函数 原型对象 的 公共成员(把 子构造函数 的原型对象 的 原型对象 变成,父构造函数的原型对象)

8.5 ❗❗ 原型链

  • 基于 原型对象继承 使得 不同构造函数原型对象 联系在一起,并且这种 关联关系 是一种 链状结构,我们将 原型对象链状结构关系 称为 原型链
    • 从实例出发,以原型对象为连接,形成的一个链式的结构
  • 由原型对象的关系相连,形成的链式结构
    • 实例成员调用的时候,优先调用自身的成员,如果自身没有,继续调用原型对象的成员,如果原型对象也没有,就调用原型对象的原型对象的成员
  • 原型链的作用: 实例调用成员的查找顺序
  • 原型链主要指的是__proto__
  • image-20220824182638259
  • image-20220818234321640
  • 🔺🔺⚠⚠总结:
    • 只要是 对象 就有 __proto__ ,指向 原型对象
    • 只要是 原型对象 里面就有 constructor,指向 创建 该原型对象 的 构造函数
    • 实例对象 和 原型对象本身 可以 访问 定义在原型身上的方法或属性
      • 构造函数不行(中间隔着prototype)
      • 构造函数访问不了原型身上的属性或方法,因为中间隔着 prototype
      • 实例对象可以访问原型对象的原型对象身上的属性和方法,中间虽然隔着东西,但能直接访问,因为这是规则
  • 原型链-查找规则
    • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
    • 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)
    • 如果还没有就查找原型对象的原型(Object的原型对象)
    • 依此类推一直找到Object为止(如果 Object.prototype 也没有得到的就是 )
    • __proto__对象原型的意义在于为对象成员查找机制提供一个方向,或者说一条路线
    • 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
      原型链

8.6 instanceof

  • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
    • 简单理解:
      • 判断 当前构造函数的原型对象 是否出现在 实例的原型链上
      • 检测 实例对象 是否 由 该构造函数 创建 出来的
  • 描述:
    • instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上
  • 语法:
    object instanceof constructor
    // object 属于 constructor 吗?
    // 实例对象 instanceof 构造函数
    
  • 参数:
    • object - 实例对象
    • constructor - 构造函数
  • 代码展示:
    // 定义构造函数
    function C(){}
    function D(){}
    
    var o = new C();
    o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype
    
    o instanceof D; // false,因为 D.prototype 不在 o 的原型链上
    
    o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
    C.prototype instanceof Object // true,同上
    
    C.prototype = {};
    var o2 = new C();
    
    o2 instanceof C; // true
    
    o instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上。
    
    D.prototype = new C(); // 继承
    var o3 = new D();
    o3 instanceof D; // true
    o3 instanceof C; // true 因为 C.prototype 现在在 o3 的原型链上
    

肆、❗❗ JavaScript高级

一、深浅拷贝

  • ❗❗ 面试重点
  • 开发中我们经常需要复制一个对象。如果直接用赋值会有下面问题:
    • 变量之间相互赋值的时候,传递的是栈中的值
    • 复杂数据类型 栈中存放的是数据地址,真正的数据存放在堆里面
  • image-20220819210150124
  • 浅拷贝深拷贝 只针对 引用类型

1.1 浅拷贝

  • 浅拷贝: 拷贝的是 地址
  • 常见方法:
    • 拷贝对象:
      • 1️⃣ Object.assgin() 或者 {...obj}
      • 2️⃣ 使用 for - in 遍历原对象,逐个拷贝属性和属性值
    • 拷贝数组:
      • Array.prototype.concat() 或者 [...arr]
      • 遍历原数组,逐个拷贝元素
  • 浅拷贝存在的问题:
    • 只能拷贝 一层 对象或数组
  • 代码展示:
    // 拷贝对象
    const pink = {
      uname: 'pink老师',
      age: 18
    };
    // 1.展开运算符 - 将pink的属性展开从新加入到这个新对象里
    // const copyPink = { ...pink };
    // 2.静态方法-拷贝对象
    const copyPink = {};
    Object.assign(copyPink, pink);
    console.log(copyPink);  // {uname: 'pink老师', age: 18}
    copyPink.uname = '刘德华';
    console.log(copyPink);  // {uname: '刘德华', age: 18}
    console.log(pink);      // {uname: 'pink老师', age: 18}
    
    // 拷贝数组
    const arr = ['迪迦', '赛罗'];
    // 1.展开运算符
    // const copyArr = [...arr];
    // 2.合并数组的方法
    const copyArr = Array.prototype.concat(arr);
    console.log(copyArr); // ['迪迦', '赛罗']
    copyArr[0] = '迪迦奥特曼';
    console.log(copyArr); // ['迪迦奥特曼', '赛罗']
    console.log(arr);     // ['迪迦', '赛罗']
    
    // 问题
    const obj1 = {
      uname: 'pink老师',
      age: 18,
      family: {
        baby: '小pink'
      }
    };
    const obj2 = { ...obj1 };
    const obj3 = {};
    Object.assign(obj3, obj1);
    obj2.family.baby = '老pink';
    console.log(obj 1, obj2, obj3);
    // {uname: 'pink老师', age: 18, family: { baby: '老pink' }}
    // {uname: 'pink老师', age: 18, family: { baby: '老pink' }}
    // {uname: 'pink老师', age: 18, family: { baby: '老pink' }} 
    
  • 直接赋值 和 浅拷贝 有什么区别?
    • 直接赋值:只要是对象,都会相互影响,因为是将栈里面的地址赋值给变量
    • 浅拷贝:如果只拷贝一层对象,不相互影响,如果出现多层对象拷贝还是会相会影响
  • 浅拷贝怎么理解?
    • 拷贝对象之后,里面的 属性值 是 简单数据类型 直接 拷贝值
    • 如果 属性值 是 引用数据类型 则 拷贝 地址

1.2 ❗❗ 深拷贝

  • 深拷贝: 拷贝的是 对象 ,不是地址
  • 🔺🔺❗❗❗面试必考题
  • 常用方法:
    • 1️⃣ 通过 函数递归 实现深拷贝
      • 递归函数介绍: 如果一个函数在 内部 可以 调用自身,那么这个函数就是递归函数
        • 函数内部自己调用自己
        • 递归函数的作用和循环类似
        • ⚠ 由于 递归 很容易 发生 “栈溢出” 错误,所以 必须加 退出条件 return
        • 递归嵌套太深出现以下错误
          Uncaught RangeError: Maximum call stack size exceeded
          超出最大调用堆栈大小
          
          image-20220820155459707
        • 代码展示:
          // 利用递归函数实现 setTimeout 模拟 setInterval效果
          function fn() {
            console.log(new Date());
            setTimeout(fn, 1000);
          }
          fn();
          
      • 实现深拷贝:
        const oldObj = {
          uname: '迪迦',
          age: 22,
          hobby: ['打怪兽', '晒日光浴', '揍银河'],
          family: {
            wife: '卡尔蜜拉',
            baby: '小迪迦'
          }
        };
        
        const newObj = {};
        // 使用递归函数实现深拷贝
        function deepCopy(newObj, oldObj) {
          // 1.遍历对象
          for (let k in oldObj) {
            // 1.处理 属性值 = 数组
            if (oldObj[k] instanceof Array) {
              newObj[k] = [];
              deepCopy(newObj[k], oldObj[k]);
            }
            // 2.处理 属性值 = 对象
            else if (oldObj[k] instanceof Object) {
              newObj[k] = {};
              deepCopy(newObj[k], oldObj[k]);
            }
            // 3.对于简单数据类型直接进行赋值操作
            // 新对象.旧对象属性名 = 旧对象属性值
            else {
              newObj[k] = oldObj[k];
            }
          }
        }
        deepCopy(newObj, oldObj);
        newObj.family.twoWife = '丽娜';
        console.log('旧对象:', oldObj);
        console.log('新对象:', newObj);
        // 旧对象:
        // {
        //   uname: '迪迦',
        //   age: 22,
        //   hobby: ['打怪兽', '晒日光浴', '揍银河'],
        //   family: {wife: '卡尔蜜拉', baby: '小迪迦'}
        // }
        // 新对象:
        // {
        //   uname: '迪迦',
        //   age: 22,
        //   hobby: ['打怪兽', '晒日光浴', '揍银河'],
        //   family: {wife: '卡尔蜜拉', baby: '小迪迦', twoWife: '丽娜'}
        // }
        
      • ❗❗ 面试总结: 用递归函数实现过深拷贝吗?
        • 1️⃣ 深拷贝:新对象不会影响旧对象
        • 2️⃣ 实现深拷贝需要用到函数递归
        • 3️⃣ 普通拷贝直接进行赋值操作
          • 如果遇到数组,再次调用递归函数解决数组
          • 如果遇到函数,直接进行赋值操作(函数就是为了实现代码的复用)
          • 如果遇到对象,再次调用递归函数解决对象
          • ⚠ 先解决数组或函数 再解决对象(数组和函数也属于对象)
    • 2️⃣ lodash里面的cloneDeep (JS库)
      • 使用之前要先引入 lodash.js
      • 语法: const newArr / newObj = _.cloneDeep(oldArr / oldObj)
      • 返回值:拷贝完的对象
    • 3️⃣ 通过 JSON.stringify() 实现
      • 语法:
        JSON.parse(JSON.stringify(Obj))
        
      • 原理:
        • 1️⃣ 将 对象 转换成 JSON字符串 ,基本数据类型
        • 2️⃣ 然后再转换为复杂数据类型,再从堆里面开辟一个新的空间,前后两个对象没有任何关系
      • 缺点: (面试)
        • 并不能转换所有的值
        • 不识别 undefined 和 函数

二、异常处理

  • 提升代码的健壮性

2.1 throw抛异常

  • 基本语法:
    throw new Error('要抛出的信息')
    
  • 总结:
    • throw 抛出异常信息,程序也会 终止执行
    • throw后面跟的是错误提示信息
    • Error对象配合throw使用,能够设置更详细的错误信息

2.2 try / catch 捕获异常

  • 捕获错误信息(浏览器提供的)
    • try - 尝试(可能发生错误的代码)
    • catch - 拦住
    • finally - 最后(不管程序是否正确,一定会执行的里面的代码)
    • ⚠ 不会自动中断程序(想要中断需要加return)
  • 总结:
    • try...catch用于捕获错误信息
    • 将预估可能发生错误的代码写在try代码段中
    • 如果try代码段中出现错误后,会执行catch代码段,并截取到错误信息
    • finally不管是否有错误,都会执行
  • 代码展示:
    // 不一定要写在函数里面
    function fn() {
      try {
        const body = document.body;
        body.style.backgroundColor = 'purple';
      } catch(err) {
        // 拦截错误,提示浏览器提供的额错误信息,但是不中断程序
        // message:必须写(属性:错误的详细信息),err是自定义的
        // console.log(err.message);
        // 要想中断程序必须加return
        // return
    
        // 还不如直接写throw
        throw new Error('选择器错误');
      } finally {
        alert('不管程序是否正确,一定会执行的代码');
      }
    }
    fn();
    

2.3 debugger

  • 类似与打断点
  • 调式的时候使用
  • 使用:直接添加到代码对应位置

三、处理this

3.1 this指向

  • 普通函数this
    • 谁调用指向谁
    • 🔺 独立的普通函数(不归属于任何对象)this指向window
      • 代码展示:
        let obj = {
          name: '阿茶',
          hi: function() {
            console.log(this);	// this -> obj
            let fun = () => {
              console.log(this);	// this -> obj
              let fn = () => {
                console.log(this);	// this -> obj
                let count = function () {
                  console.log(this);	// this -> window
                  let amount = () => {
                    console.log(this);	// this -> window
                  }
                  amount();
                }
                count();
              }
              fn();
            }
            fun();
          }
        };
        obj.hi();
        
    • 普通函数 没有 明确调用者 时this指向window
    • ❌ 严格模式下没有调用者时this指向undefined,严格模式写在当前作用域或全局作用域的最前面 ('user strict'
  • 箭头函数的this
    • 箭头函数中的this与普通函数完全不同,也不受调用方式的影响,箭头函数不存在this
    • 箭头函数自己不会创建this,它只会沿用本体所在作用域的上层作用域的this
    • 不适用: 构造函数,原型函数,dom事件函数
      定义: this 是一个使用在 作用域 内部的 关键字(包括函数作用域 和 全局作用域)

3.2 ❗❗ 改变this

  • 1️⃣ call()
    • 使用call方法调用函数,同时 改变 被调用函数 中this的值
    • 语法:
      函数名.call(thisArg, arg1, arg2, ...)
      
    • 参数:
      • thisArg:在函数运行时指定this
      • arg1, arg2:函数的实参
      • ⚠ 注意:如果写入一个简单数据类型替换this,会自动使用 基本包装数据类型简单数据 转换为 复杂数据类型
    • 返回值: 函数的返回值,因为它就是在调用函数
    • 特点:
      • 立即调用 函数(不适合用作 定时器处理函数 和 事件处理函数 )
    • 作用:
      • 调用函数
      • 改变 this 指向
    • 应用场景: 伪数组借用数组方法
    • 代码展示:⬇
  • 2️⃣ ✔ apply()
    • 语法:
      函数名.apply(thisArg[, argsArray]);
      
    • 参数:
      • thisarg:在函数运行时指定的this的值(this指向谁,不改变指向使用null
      • argsArray:函数的实参,必须包含在 数组 里面
    • 返回值: 函数的返回值,因为它就是在调用函数
    • 特点:
      • 立即调用 函数(不适合用作 定时器处理函数 和 事件处理函数 )
    • 作用: 可以以数组的形式给某些 功能函数 传参
    • 使用场景: 求数组的最值(1.for循环遍历 2.展开运算符 3.apply)
    • 代码展示:⬇
      const obj = {uname: '迪迦', age: 22};
      function fun(x, y) { 
        console.log(this); 
        console.log(x + y);
      }
      fun.apply(obj, [3, 4]);	// obj, 7
      
      const max = Math.max.apply(Math, [34, 65, 12, 30]);	// 65
      const min = Math.min.apply(Math, [34, 65, 12, 30]);	// 12
      
    • call 和 apply 的区别:
      • 都是调用函数,都能改变this指向
      • 参数不一样
        • call 传递的是多个值
        • apply 是以数组的形式给函数传递参数
  • 3️⃣ ✔🔺bind()
    • bind()方法 不会调用函数 ,但是能改变函数内部this指向
    • 语法:
      函数名.bind(thisArg, arg1, arg2, ...)
      
    • 参数:
      • thisArg:this指向谁
      • arg1, arg2, …:给函数传递参数
    • 返回值:
      • 由指定的this值和初始化参数改造的 原函数拷贝(新函数)
      • 会返回一个新的函数,一个已经改变好 this 指向的函数
    • 特点: 不会调用函数
    • 只想改变this指向,并不想立即调用函数的时候,可以使用bind(定时器内部的this,注册事件)
    • 代码展示:⬇
  • 代码展示:
    const obj = { uname: '迪迦', age: 22 };
    function fun(x, y) {
      console.log(this);
      console.log(x + y);
    }
    // 1、call()方法:
    // 语法:fun.call(thisArg[, arg1[, arg2 [, ...]]]);
    // 参数:
    //  1)要指向谁
    //  2)多个参数,用来给函数传递实参
    // 返回值:函数的返回值(call本质上就是在调用函数)
    fun.call(obj, 3, 4);  // obj, 7
    
    // 2.apply()方法:
    // 语法:fun.apply(thisArg[, [arg1, arg2, ...]])
    // 参数:
    //  thisArg:this指向谁
    //  第二个参数是 数组 ,用来给函数传递实参
    // 返回值:函数的返回值(apply本质上就是在调用函数)
    // 使用场景:求数组的最值 (1:for循环遍历 2:展开运算符 3:apply)
    fun.apply(obj, [4, 5]); //  obj, 9
    // 最值
    const arr = [23, 67, 10, 89, 20];
    console.log(Math.max.apply(Math, arr)); // 89
    console.log(Math.min.apply(Math, arr)); // 10
    
    // 3.bind()方法:
    // 语法:fun.bind(thisArg, arg1, arg2, ...)
    // 参数:
    //  thisArg:this指向谁
    //  arg1, arg2:用来给函数传递参数
    // 返回值:一个已经改变好this指向的函数(和原函数一样,除了this指向)
    // 特点:不立即调用函数
    const fn = fun.bind(obj, 4, 6);
    console.log(fn);
    fn();
    // bind应用
    const btn = document.querySelector('button');
    btn.addEventListener('click', function () {
      this.disabled = true;
      setTimeout(function () {
        this.disabled = false;
      }.bind(this), 2000);
    });
    

3.3 小结

  • 相同点:
    • 都可以改变函数内部的 this 指向
  • 区别点:
    • callapply立即调用函数,并且改变函数内部的this指向
    • callapply 传递的 参数不一样call传递参数 arg1, arg2, ...形式,apply 必须数组 形式 [arg]
    • bind 不调用函数,可以改变函数内部的this指向
  • 主要应用场景:
    • call 调用函数 并且可以传递参数
    • apply 经常跟 数组 有关,比如借助于数学对象实现数组最值
    • bind 不调用函数,但是还想改变this指向,比如改变 定时器内部的this指向

四、性能优化

4.1 防抖

  • 防抖(debounce)
    • 事件发生一段时间再执行,如果这段时间内继续触发新的事件,那么取消之前的事件,只执行最新的事件;
    • 延迟执行;
  • 使用场景:
    • 搜索框(定时器)
    • 代码展示:
      <input type="text">
      <script>
          const input = document.querySelector('input');
          let timerId = null;
          input.addEventListener('input', () => {
              // 先清除之前的定时器
              clearTimeout(timerId);
              // 在开启当前的定时器
              timerId = setTimeout(() => {
                  console.log(11);
              }, 500);
          });
      </script>
      

4.2 节流

  • 节流(throttle):
    • 一段时间内只执行一次事件,执行结束后才能继续执行新的事件。(限制任务执行频率的一种手段);
    • 节流是一种手段,来限制任务执行频率,规则是当前任务执行结束之前,不接受新任务,直到当前任务结束,才开始执行下一个任务;
  • 使用场景:
    • 轮播图点击效果、鼠标移动、页面尺寸缩放(resize)、滚动条滚动
  • 实现节流:
    • 起始时间 - 当前时间
  • 代码展示:
    <style>
      * {
         margin: 0;
         padding: 0;
       }
    
      .box {
         width: 100px;
         height: 100px;
         background-color: red;
         margin-top: 30px;
         transition: all 1s linear;
       }
    </style>
    
    <body>
      <button>按钮</button>
      <div class="box"></div>
      <script>
      const btn = document.querySelector('button');
          const box = btn.nextElementSibling;
          // 声明变量:作为box的初始宽度
          let width = 100;
          // 声明变量:节流阀
          let flag = true;
          // 事件侦听
          btn.addEventListener('click', () => {
              if (flag) {
                  flag = false;
                  width += 100;
                  box.style.width = width + 'px';
              }
          });
          // 拓展:transitionend - CSS过渡完触发事件
          box.addEventListener('transitionend', () => {
              flag = true;
          });
      </script>
    </body>
    
  • ❗❗❗ 面试 节流 和 防抖 的区别?
    • 节流: 一段时间内只执行一次事件,执行结束后才能继续执行新的事件。(限制任务执行频率的一种手段);
      • 是一种手段,用来限制任务的执行频率,规则是当前任务结束之前,不会接收新任务,直到当前任务结束,才会执行新任务
      • 采用两个时间(事件开始执行时间 - 事件执行结束时间)相减的方式实现,如果相减结果大与指定时间就调用函数
    • 防抖: 事件发生一段时间再执行,如果这段时间内继续触发新的事件,那么取消之前的事件,只执行最新的事件;
      • 定时器方式实现:在事件触发过程中一直清除定时器,当事件在一定时间内不触发就调用函数
    • 使用场景:
      • 节流:轮播图点击切换按钮、鼠标移动、页面尺寸缩放、滚动条滚动
      • 防抖:搜索框
  • 案例拓展:
    • timeupdate 事件在 视频 / 音频 当前的播放位置 发生改变 时触发
    • loadeddata 事件在 当前帧的数据加载完成 且还 没有足够的数据 播放视频 / 音频的下一帧 时触发
    • currentTime 可读写 属性:获取 当前 视频 / 音频时间
    • transitionend 事件在CSS完成过渡后触发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/18600.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

freeRTOS中使用看门狗的一点思考

关于看门狗想必各位嵌入式软件开发的朋友应该都不会陌生的。在嵌入式软件开发中&#xff0c;看门狗常被用于监测cpu的程序是否正常在运行&#xff0c;如果cpu程序运行异常会由看门狗在达到设定的阈值时触发复位&#xff0c;从而让整个cpu复位重新开始运行。 看门狗的本质是一个…

【C++初阶】类和对象(下)

一.再谈构造函数 构造函数其实分为&#xff1a; 1.函数体赋值 2.初始化列表 之前所讲到的构造函数其实都是函数体赋值&#xff0c;那么本篇文章将会具体讲述初始化列表。 初始化列表 语法 以一个冒号开始&#xff0c;接着是一个以逗号分隔的数据成员列表&#xff0c;每个"…

Kali Linux 操作系统安装详细步骤——基于 VMware 虚拟机

1. Kali 操作系统简介 Kali Linux 是一个基于 Debian 的 Linux 发行版&#xff0c;旨在进行高级渗透测试和安全审计。Kali Linux 包含数百种工具&#xff0c;适用于各种信息安全任务&#xff0c;如渗透测试&#xff0c;安全研究&#xff0c;计算机取证和逆向工程。Kali Linux 由…

【论文】SimCLS:一个简单的框架 摘要总结的对比学习(1)

SimCLS:摘要总结的对比学习(1&#xff09; 写在最前面模型框架 摘要1 简介 写在最前面 SimCLS: A Simple Framework for Contrastive Learning of Abstractive Summarization&#xff08;2021ACL会议&#xff09; https://arxiv.org/abs/2106.01890 论文&#xff1a;https://…

GIT常用操作

GIT基本使用保姆级教程 1、本地安装GIT 1.1、安装 GIT安装包获取&#xff1a;https://git-scm.com/ 具体安装流程自行百度或自行摸索 1.2、配置信息 安装完成后运行git程序&#xff0c;大打开git bash界面&#xff0c;然后输入以下命令&#xff0c;设置全局用户名与全局邮…

软件设计师笔记--数据结构

文章目录 前言学习资料数据结构大 O 表示法时间复杂度线性结构和线性表线性表的顺序存储线性表的链式存储栈的顺序存储栈的链式存储队列的顺序存储与循环队列 串KMP 数组矩阵树二叉树二叉树的顺序存储结构二叉树的链式存储结构二叉树的遍历平衡二叉树二叉排序树最优二叉树(哈夫…

ZZS-7系列分闸、合闸、电源监视综合控制装置ZZS-7/1 ac220v

ZZS-7系列分闸、合闸、电源监视综合控制装置 系列型号&#xff1a; ZZS-7/1分闸、合闸、电源监视综合控制装置 ZZS-7/11分闸、合闸、电源监视综合控制装置 ZZS-7/12分闸、合闸、电源监视综合控制装置 ZZS-7/13分闸、合闸、电源监视综合控制装置 ZZS-7/14分闸、合闸、电源…

分享111个Java源码,总有一款适合您

分享111个Java源码&#xff0c;总有一款适合您 源码下载链接&#xff1a;https://pan.baidu.com/s/1fycjYHA7y6r-IH8H7v5XKA?pwdag8l 提取码&#xff1a;ag8l ​ Druid v1.2.15 OpenJDK Java开发环境 v21.5 Diboot轻代码开发平台 v2.8.0 blockj 基础区块链&#xff08;联…

ANSYS APDL谐响应分析——悬臂梁的频响函数计算以及幅值、角度(相位)、分贝计算

问题描述 研究一根悬臂梁&#xff0c;材质为钢材。长度 L 2 L2 L2 米&#xff1b;截面为矩形&#xff0c;矩形的长度为 H 5 c m H 5cm H5cm&#xff0c;宽度为 B 2 c m B 2cm B2cm 。 建模思路&#xff1a; 先建立节点&#xff0c;然后用节点生成单元。使用n命令&…

《基于多尺度特征提取的少样本脉搏波形轮廓分类》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课…

索引—MySQL

文章目录 1.定义以及相关知识1.1定义1.2数据库保存数据的基本单位 2.MySQL中索引分类2.1B树和B树2.2主键索引&#xff08;聚簇索引&#xff09;2.3非聚簇索引2.4覆盖索引2.5复合索引&#xff08;联合索引&#xff09;2.6基于B树的索引2.7hash索引 1.定义以及相关知识 1.1定义 …

数据导向下制造业的生产效率、交易效率提升办法

在智能制造和工业4.0成为趋势的今天&#xff0c;大部分制造业企业&#xff0c;均已在企业内部通过实施PLM系统&#xff08;Product Lifecycle Management&#xff0c;产品生命周期管理系统&#xff09;&#xff0c;实现了对组织内产品研发过程和产品研发数据的管理&#xff0c;…

基于Spring Boot的在线考试系统

系统分析 可行性分析 一个完整的系统&#xff0c;可行性分析是必须要有的&#xff0c;因为关系到系统生存问题&#xff0c;对开发的意义进行分析&#xff0c;能否通过本系统来补充线下在线考试管理模式中的缺限&#xff0c;去解决其中的不足等&#xff0c;通过对本系统&#…

基于SpringBoot, Vue实现的校园二手书交易系统

背景 在Internet高速发展的今天&#xff0c;计算机的应用几乎完全覆盖我们生活的各个领域&#xff0c;互联网在经济&#xff0c;生活等方面有着举足轻重的地位&#xff0c;成为人们资源共享&#xff0c;信息快速传递的重要渠道。在中国&#xff0c;网上管理的兴起也同时飞速发…

【Linux】基本权限

&#x1f601;作者&#xff1a;日出等日落 &#x1f514;专栏&#xff1a;Linux 任何值得到达的地方&#xff0c;都没有捷径。 目录 Linux权限: 权限的概念&#xff1a; Linux上面的用户分类&#xff1a; Linux权限管理 文件访问者的分类&#xff08;人&#xff09; 文件…

金兰组织 | 2023金兰解决方案集经营管理篇正式发布

为助力企业创新管理、提质增效&#xff0c;人大金仓携手金兰组织成员单位&#xff0c;于近期发布多项经营管理领域的联合解决方案&#xff0c;共享创新应用成果。 /人大金仓高级副总裁宋瑞/ 人大金仓高级副总裁宋瑞在致辞中表示&#xff1a;“联合解决方案创新是指通过把不同领…

跟着我学 AI丨ChatGPT 详解

随着人工智能的发展&#xff0c;聊天机器人成为了一个备受关注的领域。而ChatGPT作为其中的佼佼者&#xff0c;其功能和技术水平也越来越受到人们的关注。那么&#xff0c;什么是ChatGPT&#xff1f;它又有哪些优点和限制呢&#xff1f; ChatGPT是一款基于自然语言处理技术开发…

178_技巧_Power BI 动态排名多项展示

178_技巧_Power BI 动态排名多项展示 一、背景 在 Power BI 中做排名矩阵时&#xff0c;我们经常遇到同一维度下&#xff0c;多项展示排名的问题。类似这样的排名矩阵&#xff0c;排名的名次不会太多&#xff0c;但是同一维度下会有多项同时展示排名&#xff0c;并且还要满足…

第10 CURD操作与RedisCache缓存的强制清理的实现

using System.Net; using Microsoft.Extensions.Caching.Distributed; using Core.Caching; using Core.Configuration; using StackExchange.Redis; namespace Services.Caching { /// <summary> /// 【Redis分布式缓存数据库软件管理器--类】 /// <remarks>…

2023零售店铺管理系统最新排名,这5款性价比高!

很多零售店铺的老板&#xff0c;每天都在被开单收银、记账对账、商品销售、销售数据等各种琐事困扰&#xff0c;使用传统的人工管理模式&#xff0c;耗费了大量的时间和成本&#xff0c;也没有达到理想的效果。 其实&#xff0c;零售店铺管理也可以很简单省事&#xff0c;借助零…