是js高级啊~

news2024/12/25 2:22:38

JavaScript高级

1.函数

1.1. this绑定规则优先级

  • 隐式绑定的this 大于 默认的this
  • 显式绑定的this 大于 隐式绑定的this
  • new绑定this 大于 隐式绑定的this
  • new绑定的this 大于 bind绑定的优先级
  • bind绑定的this 大于 apply、call绑定的this

备注:

  • new关键字不可以和call/apply一起使用,但是可以和bind一起使用
    • 因为bind返回的是一个新的函数
  • 隐式绑定: 浏览器执行的时候自动绑定,比如之际调用一个函数,this就是window
  • 显示绑定: 手动的设置this的指向,比如 foo.call(‘aa’)
function foo() {
    console.log(this)
}
// 显示绑定的优先级高于隐式绑定
// 1.1  apply,call 高于默认绑定
let obj = {foo:foo}
foo.call(obj)  // {foo: foo} 

// bind绑定高于默认绑定
let bar = foo.bind('aaa')
let obj = {name:'zd', baz:bar}
obj.baz()  // aaa

// new绑定的优先级高于隐式绑定
  let obj = {
        name: "zs",
        baz: foo,
      };
  obj.baz(); // {name: 'zs', baz: ƒ}
  new obj.baz(); // {}

// new绑定的优先级高于隐式绑定
// bind 会返回一个新的函数
  let bindFn = foo.bind("aaa");
  bindFn(); // aaa
  new bindFn(); // {}

// bind绑定的this 高于 apply、call绑定的this
  let bindFn = foo.bind("aaa");
  bindFn(); // aaa
  bindFn.call("bbb"); // aaa
  bindFn.apply("bbb"); // aaa

1.2.this绑定之外的情况

  • 非严格模式下,显示的将this绑定成null或undefined,this的指向是默认绑定(window)
  • 严格模式下,显示的将this指向null或undefined,this指向就是null或者undefined
function foo() {
    console.log(this);
}

foo.call(null);  //  window对象
foo.call(undefined);  // window对象
foo.call("q");  //  包装类 String{q}  就是字符串q

// 
  • 非严格模式下,创建一个函数的间接调用,this的指向是默认指向
  • 由以下案列可知,赋值语句的返回值是要赋的值
let obj1 = {
    name: "obbj1",
    foo: function () {
        console.log(this);
    },
};

let obj2 = {};
console.log((obj2.foo = obj1.foo)); // foo函数
// 因为赋值过程的返回值是当前函数,直接调用就是全局调用,this指向window
(obj2.foo = obj1.foo)(); // this指向window

1.3.箭头函数

  • 箭头函数不会绑定this和arguments和super

  • 箭头函数没有显式原型

  • 箭头函数不能用作构造函数(因为没有显示原型)

  • 如果函数体只有一行代码,默认值返回的是一个对象,必须用括号将对象包裹

    • // 如果不用括号包裹   () => {}  这样js引擎解析不出来
      [1,2,3].map(item => ({a:1,b:2})) 
      
  • 箭头函数中没有this绑定

    • // 那为什么再箭头函数中可以使用this
      // 因为再当前作用域中没有会向上一级作用域找
      let foo = () => {
          console.log(this)
      }
      // 正常情况下,bind绑定this -> aaa
      // 但是fn的this是window,并没有替换成aaa
      // 由此可见 箭头函数中没有this
      let fn = foo.bind('aaa')
      fn()   // window对象
      
      

1.4.函数的属性

  • name :表示当前函数的函数名
  • length:返回当前函数的参数(形参)
    • rest(剩余参数)参数是不参与参数的个数

1.5.arguments

  • arguments是一个类数组对象

arguments转换成数组

foo(1,2,3,4)
function foo() {
    let arr;
    //方法0 遍历
    // 方法一
    arr = Array.from(arguments)
    // 方法二
    arr = [...arguments]
    // 方法三
    // slice返回的是截取后的数组,没有参数,截取全部
    arr = [].slice.apply(arguments)
}

1.6.纯函数

函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式

纯函数的维基百科定义

  • 在程序设计中,若有一个函数符合以下的条件,那么这个函数就被称之为纯函数
  • 此函数在相同的输入值时产生相同的输出
  • 函数的输入和输出值意外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关
  • 该函数不能有语义上可观察的函数副作用,诸如,‘触发事件’,使输出设备输出,或更改输出值以外的内容等
// 调用这个函数,比如输入10,20,结果是30
// 那么在任何地方foo(10,20) 值都是30
function foo(num1,num2) {
    return num1 + num2
}
foo(10,20) //30
foo(10,20) //30

// 以下的情况,如果num发生了变化,那么输出的结果也会变化,不符合纯函数的定义,所以就不是纯函数
var num = 100
function foo(num1, num2) {
    return num1 + num2 + num 
}

foo(10,20) //130
num = 200
foo(10,20) // 230

总结纯函数

  • 确定的输入,一定会产生相同的输出
  • 函数在执行过程中,不能产生副作用

1.6.1.函数副作用

  • 副作用(side effect)其实本身是医学的一个概念,比如经常说吃什么要会有副作用
  • 在计算机科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值外,还对调用函数产生了附加影响,比如修改了全局变量,修改参数挥着改变外部的存储

1.7.柯里化函数

  • 柯里化也属于函数式编程里面的一个非常重要的概念
  • 不仅被用于JavaScript,还被用于其他编程语言

维基百科解释:

  • 在计算机科学中,柯里化(Currying),又被译为卡瑞化、加里化
  • 把接收多个参数的函数,变成接收一个单一参数的函数,并且返回接收余下的参数,而且返回结果的新函数的技术

自己的理解:

  • 传递给函数一部分参数来调用它,让他返回一个函数去处理剩余的参数,这个过程就称之为柯里化
  • 柯里化是一种函数的转换,将一个函数从可调用的f(x,y,z),转换为可调用的f(x)(y)(z)
  • 柯里化不会调用函数,他只是对函数进行转换
function foo(x,y,x) {
    return ...
}
foo(10,20,30)

// 将上面的函数转化成下面的函数的过程就是柯里化

function foo(x) {
    return function(y) {
        return function(z) {
            return ...
        }
    }
}
foo(10)(20)(30)
    

1.7.1.使用箭头函数转换柯里化

let foo = x => y => z => console.log(x,y,z)
foo(1)(2)(3) // 1,2,3

1.7.2.自动化柯里化函数封装

function autoCurrying(fn) {
        return function currying(...arg) {
          if (arg.length >= fn.length) {
            return fn(...arg);
          } else {
            return function (...newArr) {
              console.log(arg);
              return currying(...arg.concat(newArr));
            };
          }
        };
      }

      function foo(x, y, z) {
        // console.log(x + y + z);
        console.log(x + y + z);
      }

      let autoFoo = autoCurrying(foo);
      autoFoo("aa")("bb")("cc"); // aabbcc

1.8.组合函数

  • 组合函数(Compose Function)是在JavaScript开发中的一种对函数的使用技巧、模式
// 有以下场景,首先对一个数取平方,再乘以2,我们需要用到两个函数才能完成,显得有些麻烦
function dobule(num) {
    return num * 2
}
function pow(num) {
    return num ** 2
}

console.log(dobule(pow(2))) // 16

// 将其转换成组合函数
function composeFn(num) {
    return dobule(pow(num))
}
console.log(composeFn(2)) // 16

1.8.1.自动化组合函数的封装

function compose(...fns) {
  // 1. 边界判断
  let len = fns.length;
  if (!len) return;
  for (let i = 0; i < len; i++) {
    let fn = fns[i];
    if (typeof fn !== "function")
      throw new Error(`index positon ${i} must be function `);
  }

  //
  return function (...args) {
    // 因为做了边界判断,所以第一项一定存在
    // 如果直接遍历不取第一项的值,那么每次遍历传的值都是一样的,取了第一次的结果存起来,下次的参数就是这个结果

    let result = fns[0].apply(this, args);
    for (let i = 1; i < len; i++) {
      let fn = fns[i];
      // 参数加[]是因为apply调用的,参数必须是数组,使用apply可以指定this 更加灵活
      result = fn.apply(this, [result]);
    }

    return result;
  };
}

function foo(num) {
  return num * 2;
}

function fn(num) {
  return num ** 2;
}

function fn1(num) {
  return num ** 2;
}

let newFn = compose(foo, fn, fn1);
console.log(newFn(2)); // 256


2.浏览器原理

2.1.页面渲染过程

2.1.2.回流和重绘

2.1.2.1.回流

介绍:

  • 也称为重排
  • 浏览器解析HTML文件时,第一次确定节点的大小和位置称为布局(layout)
  • 之后对节点的大小、位置修改重新计算称之为回流

引起回流的情况

  • 比如DOM结构发生了改变(添加一个新的节点或移除节点)
  • 比如改变了布局(修改了width、height、padding、font-size等值)
  • 比如窗口resize(修改了窗口的尺寸)
  • 比如调用getComputedStyle方法获取尺寸、位置信息

2.1.2.2.重绘

介绍:

  • 浏览器解析HTML文件时,第一次渲染内容称之为绘制(paint)
  • 之后重新渲染称之为重绘

引起重绘的情况

  • 比如修改了背景色、文字颜色、边框颜色、样式等

2.1.3.如何避免发生回流

回流一定会引起重绘,所以回流是一件很消耗性能的事情

  • 修改样式时,尽量一次性修改
    • 比如使用cssText进行修改,或者使用class进行修改
  • 尽量避免频繁的操作DOM
    • 使用DocumentFragment或者将要操作的元素拼装好再一并追加到元素上
  • 尽量避免通过getComputedStyle获取尺寸、位置等信息
  • 对某些元素使用position的absolute或者fixed
    • 这种情况也会引起回流、但是开销相对较小、不会对其他的元素造成影响
    • 这些属性会让元素脱标,重排的时候不会影响标准流的元素

2.1.4.特殊解析-composite

  • 绘制过程中,可以将布局后的元素绘制到都多个合成图层中
    • 这是浏览器的一种优化手段
  • 默认情况下,标准流的内容都是被绘制在同一个图层(layers)中
  • 而一些特殊的属性,会创建一个新的合成层(compositeLayers),并且新的图层可以利用 GPU来加速绘制
    • 因为每个合成层都是单独渲染的

常见的可以从形成新的合成层的属性:

  • 3D transforms
  • video、canvas、iframe
  • opacity动画转换时
  • position:fixed
  • will-change
    • 一个实验的属性,提前告诉浏览器元素可能发生哪些变化
  • animation或transition设置了opacity、transform

分成确实可以提高性能,但是他以内存管理为代价,因此不应该最为web性能优化策略的一部分过度使用

2.2.script元素和页面解析的关系

  • 浏览器在解析HTML过程中,遇到了script元素是不能继续构建DOM树的
  • 他会停止继续构建,首先下载js代码,并且执行js脚本
  • 只有等到js脚本执行结束后,才会继续解析HTML,构建DOM树

上述的原因

  • 因为js的作用之一就是操作DOM,并且可以修改DOM
  • 如果等到DOM数构建完成并且渲染在执行js,会造成严重的回流和重绘,影响页面的性能
  • 使用script元素提供的两个属性defer和async解决

2.2.1.defer

  • defer属性告诉浏览器不用等待脚本下载,再去解析HTML
    • 脚本会由浏览器进行下载,但是不会阻塞DOM Tree的构建过程
    • 如果脚本提前下载好了,他会等待DOM Tree构建完成,在DOMContentLoaded时间之前执行defer中的代码
  • 多个带defer的脚本时可以保持正确的顺序执行
  • 从某种角度来说,defer可以提高页面的性能,并且推荐放到head元素中
  • 注意:defer仅适用于外部脚本

2.2.2.async

  • async也能够让脚本不阻塞页面
  • async是让一个脚本完全独立的
    • 浏览器不会因async脚本而阻塞
    • async脚本不能保证顺序,他是独立下载,独立运行,不会等待其他脚本
    • asyn不能保证在DOMContentLoaded之前或者之后执行
  • defer通常适用于需要在文档解析后操作DOM的js代码,并且对多个script文件有顺序要求的
  • asyn通常用于独立的脚本,对其他脚本,甚至DOM没有依赖的

3.严格模式

  • 严格模式通过抛出错误来消除一些原有的静默错误
  • 严格模式让js引擎再执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)
  • 严格模式禁用了再ECMAScript未来版本中可能定义的一些语法
  • 可以给某个文件开启严格模式,也可以单独给函数开启严格模式

开启严格模式后

  • 无法意外的创建全局变量
  • 严格模式静默失败也报错
  • 不允许函数参数有相同的名称
  • 不允许0的八进制语法
  • 不允许使用with
  • …查mdn

4.数据属性修饰符

4.1.Object.defineProperty

  • 数据属性

    • configurable
      • 默认值true
      • 表示属性是否可以通过delete删除。
      • 一旦设置后续将不能修改
    • enumerable
      • 默认值true
      • 是否可枚举
    • writable
      • 默认值true
      • 是否可修改
    • value
      • 默认情况下时undefined,(通过definedProperty设置属性的情况是)
  • 存储属性

    • configurable
    • enumerable
    • set
      • 设置属性时会执行的函数,默认值时undefined
    • get
      • 获取属性时会执行的函数,默认值时undefined(不写get时默认情况)
  • 使用存储属性对对象的属性进行操作

    //  1. 使用Object.defineProperty给obj添加一个name的属性
    // 2. 外界访问name 其实就是访问_name的值,修改也是_name的值
    // 因为return this.name
    let obj = {
            _name: 'zs'
          }
    
          Object.defineProperties(obj, {
            _name: {
              enumerable: false,
              configurable: false
            },
            name: {
              get() {
                // 不能直接返回name 不然会造成死循环
                return this._name
              },
              set(val) {
                this._name = val
              },
              enumerable: true,
              configurable: false
            }
          })
    
    

5.原型&原型链

  • 原型分为对象原型和函数原型

5.1.对象原型(隐式原型)

注意:对象中是不能用对象.prototype访问的,对象的原型称为隐式原型

  • JavaScript中,每一个对象都有一个特殊的内置属性[[prototype]],这个特殊的对象可以指向另外一个对象
  • 通过属性key来获取一个value时,会触发[[Get]]的操作
  • 首先会检查该对象中是否有对应的属性,有的话就使用,没有就访问[[property]]中的

获取对象原型的方式

  • 通过对象的__proto__属性可以获取到(但是这是早期浏览器自己添加的,存在一定的兼容性)
  • 通过Object.getPrototypeOf方法获取

5.2.函数原型(显示原型)

函数中的原型叫做显示原型

  • 可以通过.prototype来获取原型
  • 可以通过Object.getPrototypeOf方法获取
  • 可以通过对象的__proto__属性可以获取到(但是这是早期浏览器自己添加的,存在一定的兼容性)
  • 作用是再使用new操作符的时候,将函数的显示原型赋值给对象的隐式原型,这样对象就可以使用了

5.3.ES5实现继承

寄生组合式继承(思想)

      // 函数作用: 创建一个空对象,对象的隐式原型指向o
      function createObject(o) {
        function F() {}
        F.prototype = o;
        return new F();
      }
      // 继承的函数
      function inherit(subType, superType) {
        // 如果适配早期的浏览器,那么就不能使用Object.crate()
        // subTyp.prototype = Object.create(superType.prototype);
        subType.prototype = createObject(superType.prototype);
        Object.defineProperty(subType.prototype, "constructor", {
          enumerable: false,
          configurable: true,
          writable: true,
          value: subType,
        });
      }

      // 实现继承

      function Person(name, age) {
        this.name = name;
        this.age = age;
      }
      Person.prototype.say = () => console.log("say Person");

      function Student(name, age, score) {
        Person.call(this.name, age);
        this.score = score;
      }

      inherit(Student, Person);

      let s1 = new Student("jack", 19, 98);
      console.log(s1);

5.4.对象的方法(部分)

hasOwnProperty

  • 对象是否有某一个属于自己的属性(不是在原型上的属性)

in/for in操作符

  • 判断某个属性是否在对象或者对象的原型

instanceof

  • 用于检测构造函数的property,是否出现在某个实例对象的原型链

isPropertyOf

  • 用于监测某个对象,是否出现在某个实例对象的原型链上

5.5.函数也是对象

  • 函数也是对象
  • 也有隐式原型,他的隐式原型指向Function的显示原型(默认情况下)

6.ES6

  • 又叫ECMAScript2015或者ES2015,泛指2015及之后发布的版本的统称

6.1.class(类)

  • 本质上是构造函数和原型链的语法糖
      class Person {
        constructor(name, age) {
          this._name = name;
          this.age = age;
        }
// 原型方法
        say() {
          console.log("sss");
        }
      }

6.1.1.类中的存储器

  • set和get除了可以在Object.defineProperty中使用,还可以直接在对象中使用(不常用)

  •     let obj = {
          name1: "zd",
          _age: 18,
          get age() {
            return this._age;
          },
          set age(val) {
            this._age = val;
          },
        };
    
    
  • 所以在类中也可以使用set和get

  • 以下案列的好处是,当要获取坐标,都是成对出现的,我们还需要进行处理(写一个方法…)

  • 使用get就不用写方法,直接调用position属性即可

  • class Rectangle {
            constructor(x, y, width, height) {
              this.x = x;
              this.y = y;
              this.width = width;
              this.height = height;
            }
    
            get postion() {
              return { x: this.x, y: this.y };
            }
    
            get size() {
              return { width: this.width, height: this.height };
            }
          }
          let rect1 = new Rectangle(10, 20, 100, 200);
          console.log(rect1.position); // {"x": 10,"y": 20}
    	  console.log(rect1.size); // {"width": 100,"height": 200}
    
    

6.1.2.类的静态方法

  • 就是ES6之前的类方法

  • 通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义

  • // 静态方法的this也是指向他的调用者,通常就是类本身
    class Foo {
        constructor() {}
        static say() {
            console.log("我睡觉哦");
            console.log(this);
        }
    }
    Foo.say();
    
    

6.1.3.继承

  • 通过关键字extends来实现继承
  • class为我们提供了super关键字
    • 执行super.method(…)来调用一个父类的方法
    • 执行super(…)类调用父类constructor(只能在当前类的constructor中调用)
  • 在当前类的constructor中,必须要先使用super才能使用this(将super()放到constructor代码块的首行),否则会报错
  • super的使用位置有三个:子类的构造方法实例方法静态方法
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    say() {
        console.log(`${this.name} is say`);
    }
    drink() {
        console.log(`${this.name} is drank`);
    }
}

class Student extends Person {
    constructor(name, age, sno) {
        super(name, age);
        this.sno = sno;
    }
    drink() {
        super.drink();
    }
}

let s1 = new Student("zs", 18, 1001);
s1.say();
s1.drink();
console.log(s1);

6.1.4.多类继承

  • JavaScript是不支持多类继承的
  • 使用mixin混入的思想完成
// 有running和flayer两个类
// bird类想要继承这两个类

// class Running {
//   run() {
//     console.log('running~')
//   }
// }
// class Flayer {
//   flay() {
//     console.log('flay~')
//   }
// }

// 使用mixin混入的思想
// return class extends basicClass
// 表示 返回一个新的继承basicClass的类
// 外界接收到这个值就是一个新类并且继承了basicClasss类
function mixinRunning(basicClass) {
    return class extends basicClass {
        run() {
            console.log("running~");
        }
    };
}

function mixinFlayer(basicClass) {
    return class extends basicClass {
        flay() {
            console.log("flayer~");
        }
    };
}

class Bird {
    eat() {
        console.log("eating~");
    }
}

// 方案1
// let NewBird = mixinRunning(mixinFlayer(Bird));

// 方案2
class NewBird extends mixinRunning(mixinFlayer(Bird)) {}
let bird1 = new NewBird();
console.log(bird1);

6.2.new操作符

  • 在内存中创建一个新的对象(空对象)
  • 将这个对象内部的_proto_(隐式对象)指向该类的prototype(显示原型)
  • 构造函数内部的this,指向这个新的对象
  • 执行构造函数的内部代码
  • 如果构造函数没有返回空对象,则返回创建的新对象

6.3.多态(面向对象的知识,不是es6才出来的)

维基百科定义:

多态(polymorphism)指为不同数据类型的实体提供统一的接口,或用一个单一的符号来表示多个不同的类型

​ 总结:不同数据类型进行同一个操作,表现的行为是不同的,这就是多态

// 为不同数据类型的实体提供统一的接口
function sum(a1, a2) {
    return a1 + a2
}
// 调用了同一个接口,但是返回的结果的数据类型却不一样
// 符合多态的定义
sum(10,20)
sum('qq', 'nn')

// 用一个单一的符号来表示多个不同的类型
// foo可以是任意类型的,也符合多态的定义
var foo = 1
foo = 'aa'
foo = []


  • 从以上的案例和对维基百科的对比来说,Javascript是一定存在多态的

6.4.字面量的增强

6.4.1.属性的简写

let name = 'zs'
let obj = {
    name,
    age: 18
}

6.4.2.方法的简写

  • 对象中的函数可以直接简写
let obj = {
    drink:function() {}
    say() {}
}

6.4.3.计算属性的写法

//
let key = 'address'

let obj = {
    [key]: '河南'
}

console.log(obj) // {address: '河南'}

6.5.解构(Destructuring)

6.5.1.数组的解构

// 采用下标对下标的方式进行解构
// 如果不想声明下标是2的变量,直接空出去,后面的继续写
let arr = [1,2,3,4]
let [c1,c2, ,c3] = arr
console.log(c1,c2,c3) // 1,2,4

// 拓展练习,解构出数组
// 利用了解构和剩余参数的语法
let [c1, c2, ...c3] = arr
console.log(c1,c2,c3) // 1,2,[3,4]

 
// 解构的默认值
// 如果解构对应的值是undefined, 变量的值就是默认值
let [c1='ad',c2,c3='ss',,c4='de'] = arr
// c1 = 1    c4 = de


6.5.2.对象的解构

let obj = {
    name: 'zs',
    age: 18
}

let {name,address:wAddress='hhh'} = obj
// wAddress = hhh

// 对象解构配合剩余参数
let obj = {
    name: 'zs',
    age: 19,
    address: '河南'
}
let {name,...info} = obj
//  name='zs'  info = {age:19, address:'河南'}

6.6.let&const

作用域提升

  • let和const声明的变量,没有作用域提升,但是在代码执行的时候还是会被先创建的,只不过只有到变量被赋值的时候才可以被访问

暂时性死区

英文:TDZ(temporal dead zone)社区给的定义,官方没有这个定义

  • 从块级作用域的顶部一直到变量声明完成之前,这个变量处在暂时性死区

    • 在这个区域内,无法访问该变量
  • 暂时性死区和定义的位置没有关系,和代码的执行顺序有关系

    • function foo() {
          console.log(message)
      }
      let message = 'zs'
      foo() // zs
      
      
  • 暂时性死区形成后,再该区域内这这个标识符不能够被访问

    • let message = 'zs'
      function foo() {
          console.log(message)
          let message = 'aa'
      }
      foo()  // 报错
      
      

声明的变量不添加window上

块级作用域

  • 在块级作用域内用let或const声明的变量外界无法访问

  • 但是在块级作用域内声明的函数在外界允许访问,但是只能在声明之后才能访问

    • 因为块级作用域是es6之后才出来的,如果函数写在块级内,那么早期的代码如果在外部访问就会报错,项目中可能会用社区的库,有些库可能不维护了,所以浏览器对块级内声明的函数做了特殊的处理
    foo() // 报错
    {
        let a = 123
        function foo() {}
    }
    console.log(a) // 报错
    foo()
    
    

6.6.模板字符串

模板字符串

b = 123
a = `${b} + ddd`
//a  123 + ddd

标签模板字符串

react中会用,暂做了解

// 
      function foo(...arg) {
        console.log(arg);
      }

      foo`hahha is ${123} dog ${222}
      .banner {
        color:red;
      }
      `;
// 打印结果
      // [
      //   [
      //     "hahha is ",
      //     " dog ",
      //     "\n      .banner {\n        color:red;\n      }\n      ",
      //   ],
      //   123,
      //   222,
      // ];

6.7.函数增强

6.7.1.默认参数

  • 有默认参数的形参尽量写在后面

  • 有默认参数的形参,是不会计算在length之内的

  • 默认参数之后的所有参数都不参与计算在length之内

    • function foo(age,name=123,height=2) {}
      foo.length // 1
      
      
  • 剩余参数也是放到最后面,但是要放到默认参数之前

function foo(name='zs',age=18) {
    console.log(name,age)
}
foo()  // zs,18

6.7.2.默认参数解构

const obj = {name:'zs'}
const {name = 'jack'}  = obj


// 原始写法 
// 当形参是一个对象的时候,设置默认值
function foo(obj = {name:'zs'} ) {
    console.log(obj.name)
}
// 优化
function foo({name} = {name: 'zs'} ) {
    console.log(name)
}
// 进一步优化
function foo({name = 'zs'} = {} ) {
    console.log(name)
}
foo()

6.8.展开语法(spread syntax)

  • 函数调用时使用
  • 数组构造时使用
  • 构建对象字面量时,也可以使用展开运算符,ES9(ES2018)新添加的
    • 必须是对象字面量的形式才可以使用
// 基本使用
let obj = {
    name: 'zs'
}
let info = {...obj}

let arr = [1,2,3]
let arr1 = [...arr,9]  // [1,2,3,9]

let hello = 'hello'
let arr2 = [...hello]

6.9.Symbol

  • Symbol是ES6中新增的一个基本数据类型,翻译为符号

Symbol出现的原因:

  • 在ES6之前,对象的key只能是字符串,很容易造成属性名的冲突

  • 比如传入一个函数的参数是一个对象,函数体中对对象添加一个属性,那么如果传入的对象中已经有函数代码块中同名的key那么就会进行覆盖

    • function foo(obj) {
          obj.name = 'zs'
      }
      foo({name:'jack'})
      
      
  • Symbol就是为了解决这一问题的,用来生成一个独一无二的值

    • Symbol值是通过Symbol函数来生成的,生成后可以作为属性名
  • const s1 = Symbol()
    const obj = {
        [s1]: 'li'
    }
    obj[Symbol()] = 'sss'
    
    
  • Symbol即使创建多次,他们也是不同的

    • Symbol函数执行后每次创建出来的值都是独一无二的
  • 也可以在创建Symbol值的时候传入一个描述description(ES2019新增的特性)

    • const s1 = Symbol('我是描述哈哈')
      
      

获取用Symbol属性名的值

  • 不能使用Object.keys()获取key是symbol的键名
  • 要通过Object.getOwnPropertySymbol()获取
const s1 = Symbol()
const obj = {
    [s1]: 'hhh'
}
let symbolKeys = Object.getOwnPropertySymbol(obj)

获取两个相同的Symbol

  • 通过Symbol.for方法来做到这一点
  • 可以通过Symbol.keyFor方法来获取对应的key (当前symbol的描述)
const s1 = Symbol()
const s2 = Symbol()
consolel.log(s1 === s2) //true

const s3 = ('sdfs')
console.log(Symbol.keyFor(s3)) // sdfs

6.10.Set

  • 类似数组,但是数据不能重复
  • new Set(iteration)
    • 入参是可迭代的元素

常见的属性和方法

  • size
    • 获取集合元素中的个数
  • add(value)
    • 添加某个元素,返回Set对象本身
  • delete(value)
    • 从Set中删除和这个值相等的元素,返回Boolean类型
  • has(value)
    • 判断set中是否存在某个元素,返回Boolean类型
  • clear()
    • 清空set中所有的元素,没有返回值
  • forEach(callback [, thisArg])
    • 通过forEach遍历set
const s1 = new Set([1,2,3])
s1     //  {1,2,3}

6.7.1.WeakSet

弱引用和强引用

强引用(strong reference):

​ 设有个一变量obj,他的值是{name:‘zs’},当js的垃圾回收(GC)运行时,根据可达性的原理,发现{name:‘zs’}这个对象被obj变量所引用着,那么就不会将其清理掉,这就是强引用

弱引用(weak reference):

​ 设有一个变量obj,他的值是{name:‘zs’},虽然他也被obj引用着,但是通过别的方式将这个引用变成弱引用了,那么当GC运行时,就会忽略这个引用,然后{name:‘zs’}这个对象就可能被回收

WeakSet就是弱集合的意思,也就是说他的引用的值,就是弱引用

WeakSet和Set的区别

  • WeakSet中只能存放对象类型,不能存放基本数据类型
  • WeakSet对元素的引用是弱引用,如果他引用的值没有被其他变量引用,那么GC可以对该引用的值进行回收
  • WeakSet是不能遍历的
    • 因为WeakSet是弱引用,如果我们遍历获取其中的元素,那么可能造成对象不能正常的销毁
cosnt arr = [{name: 'zs'}]
const weakS = new weakSet(arr)

6.11.Map

  • 也是键值对形式
  • 键是任意类型

常见的属性和方法

  • size
    • 返回Map中元素的个数
  • set(key, value)
    • 在Map添加key、value,并返回整个Map对象
  • get(key)
    • 根据key获取Map中的value
  • has(key)
    • 判断是否包含某一个key,返回Boolean
  • delete(key)
    • 根据key删除一个键值对,返回Boolean
  • clear()
    • 清空所有元素
  • forEach(callback [, thisArg])
const m1 = new Map([
    ['建','值'],
    [1,2]
])

6.11.1.WeakMap

  • WeakMap的key只能使用对象,不接受其他类型作为key
  • WeakMap的key对对象的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象
  • 不支持遍历
const obj1 = {name: 'zs'}
const wm = new WeakMap()
wm.set(obj1, 123)

7.ES7~ES13新特性

7.1.ES7新特性

7.1.1.Array.includes

查询mdn

7.1.2. 指数运算符

求一个数的多少次方,用**表示

Math.pow(2,3)
2 ** 3

7.2.ES8新特性

7.2.1.Object.values

  • 获取对象中所有对值

  • const obj = {
        name: 'zs',
        age: 19
    }
    const val = Object.values(obj)
    // ['zs', 19]
    
    

7.2.2.Object.entries

  • 可以获取一个数组,数组中会存放可枚举属性的键值对的数组
const obj = {
    name: 'zs',
    age: 19
}
const ent = Object.entries(obj)
// ent  [ ["name", "zs"], ["age", 19] ]
for (let item of ent) {
	const [key,value] = item
    console.log(key,value)
}

// 如果是一个数组
console.log(Object.entries([1,2,3])) 
// [['0', 1], ['1', 2], ['2', 3]]

// 如果是字符串
console.log(Object.entries('aa'))
//[['0','a'],['1','a']]

7.2.3.字符串填充

  • padStart(targetLength[, padString])
  • padEnd(targetLength[, padString])
  • targetLength:当前字符串需要填充的目标长度,如果小于这个长度将用padString进行填充
// 格式化日期
const hh = "3".padStart(2, "0");
const mm = "15".padStart(2, "0");
console.log(`${hh}:${mm}`);
// 03:15

// 加密身份证或者银行卡
const cardNum = '411989199901011234'
const lastFour = cardNum.slice(-4)
const resNum = lastFour.padStart(cardNum.length, '*')


7.3.ES10

7.3.1.flat

  • 将一个数组,按照规定的深度遍历,将遍历到的元素和子数组中的元素组成一个新的数组,然后返回
  • flat(depth)
    • 按照深度去遍历,默认值是1
// 更多案例查询mdn文档
const nums = [1,2,[1,2],[[1,2]]]
const flatNum = nums.flat(1)
console.log(flatNum) // [1,2,1,2,[1,2]]

7.3.2.flatMap

  • 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组
  • 做了两步操作
    • 首先对数组进行map操作
    • 然后在对操作之后的数组进行flat(1)的操作
const msg = ["hello world", "hello zs"];
const newMsg = msg.flatMap((item) => item.split(" "));
console.log(newMsg); // ['hello', 'world', 'hello', 'zs']

7.3.3.fromEntries

  • 将转为entries的对象,变成没转之前的状态
const obj = {
    name: 'zs',
    age: 19
}
let objEntries = Object.entries(obj)
// [ ['name', 'zs'],['age', 19] ]
let transObj = Object.fromEntries(objEntries)
// {name: 'zs', age: 19}


7.3.4.trimStart

  • 去除首部的空格
const str = ' hello world'
const trimS = str.trimStart()
// 'hello world'

7.3.5.trimEnd

  • 去除尾部的空格
const str = ' hello world '
const trimS = str.trimEnd()
// ' hello world'

7.4.ES11

7.4.1.BigInt

  • 在早期的JavaScript中,不能正确的表示过大的数字
  • 大于Number.MAX_SAFE_INTEGER的数值,表示的可能不正确
  • 使用BigInt即可,在要表示的数后面添加n
console.log(Number.MAX_SAFE_INTEGER)
// 9007199254740991
console.log(9007199254740991 + 1) // 9007199254740992
console.log(9007199254740991 + 2) // 9007199254740992


// 使用BIGINT
console.log(9007199254740991n + 2n) // 9007199254740993
console.log(9007199254740991n + 5n) // 9007199254740996

7.4.2.空值合并运算符

  • Nullish Coalescing Operator
  • 使用??,如果??前面的值是nullundefined,那么就使用??后面的值
let info = undefined
info = info ?? '默认值'
//  info  默认值  

7.4.3.可选链操作符

  • optional chaining
  • 允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效,如果没有找到,该表达式短路返回值是 undefined
const obj = {
name:'zs'
}
obj?.asy?.()

7.4.4.Global this

  • 规范了获取全局对象使用globalThis

7.5.ES12

7.5.1.FinalizationRegistry

  • 可以让对象在被垃圾回收的时候请求一个回调
  • FinalizationRegistry提供一种方法,当一个在注册表中的对象被垃圾回收时,请求在某个时间点上调用一个清理回调(清理回调有时被称为finalizer)
  • 可以通过调用register方法,注册热河想要清理回调的对象,传入该对象和所含的值
// register的第二个参数,自定义标识符
// 如果有第二个参数,那么在执行回调的时候,参数就是传入的自定义标识符
let obj = [{ name: "zs" }];
let info = { a: 123 };
const finalization = new FinalizationRegistry((val) => {
    console.log("对象被回收了", val);
});

finalization.register(obj, "obj");
finalization.register(info, "info");

obj = null;

// 检测WeakMap是否会被GC删除
// 结果会调用回调,证明会被GC回收
// 如果将WeakMap换成Map就不会执行回调
let obj = { name: "123" };
let wp = new WeakMap();

wp.set(obj, 123);

const finalization = new FinalizationRegistry(() => {
    console.log("first");
});
finalization.register(obj);
obj = null;

7.5.2.WeakRefs

  • WeakRefs: 弱引用
let obj = { name: "zs" };
let wr = new WeakRef(obj);

let finalization = new FinalizationRegistry((val) => {
    console.log("对象被回收", val);
});

finalization.register(obj, "obj");

// 此时obj指向的对象已经被回收了,
obj = null;

7.5.3.逻辑赋值运算符

  • ||=
  • ??=
  • &&=
function foo(message) {
    // 1.||逻辑运算符
    // message = message || '默认值'
    // message ||= '默认值'
    
    // 2. ?? 逻辑运算符
    message = message ?? '默认值'
    message ??= '默认值'
    
    console.log(message)
}
foo(0)  // 0
foo()  // 默认值

let obj = {
    name:'zs'
}
obj = obj && obj.name
obj &&= obj.name

7.5.4.数字分割

  • Numeric Separator
  • 使用下划线分割较大的数字
const num = 123_0000_0000

7.5.5.字符串替换

  • String.prototype.replaceAll()
  • 可以替换全部的字符串
  • String.prototype.replace()只能替换一次

7.5.ES13

7.5.1.Object.hasOwn

  • Objcet.hasOwn(obj, propKey)
  • 用来替代Object.prototype.hasOwnProperty()方法的

7.5.2.method.at

  • 查mdn

7.5.3.class的新成员

  • Instance publish fields
    • 实例的公共字段,每个实例都有的字段
  • Static public fields
    • 类的静态字段
  • Instance private fields

  • static block
    • 类的静态代码块,当类加载的时候执行一次
class Person {
    // 所有实例都可有这个属性
    height = 1.88
	constructor() {}
	// 类的静态属性
	static address = '河南'
	// 类的静态私有属性, 只有类内部能够访问
	// 用this.访问
	static #number = 12
    // 类的静态代码块
    static {
        console.log('只有类初始化时候执行一次')
    }
}

8.Proxy

  • 一个对象进行代理,之后对该对象的所有操作都是通过代理对象来完成的
  • new Proxy(target, handler)
  • target : 要代理的对象
  • handler:被代理对象的行为
    • 内置很多个方法(只列出几个,剩余的查mdn)
const obj = {
    name: "zs",
    age: 19,
};

// target表示被代理的源对象
// key 被查看、操作的对象的属性
// val 被修改的新值
// receiver 被代理的对象
const proxyObj = new Proxy(obj, {
    set(target, key, val, receiver) {
        console.log(`监听了${key},值是${val}`);
        target[key] = val;
    },

    get(target, key) {
        console.log(`监听了${key}`);
        return target[key];
    },

    deleteProperty(target, key) {
        delete target[key];
    },

    has(target, key) {
        console.log(`${key}被判断是否存在${target}`);
        return key in target;
    },
});

proxyObj.name = "jack";
proxyObj.address = "河南";
proxyObj.age;
delete proxyObj.address;
console.log("age" in proxyObj);

9.Reflect

  • Reflect是一个对象,字面意思是反射
  • 主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法
  • Object对象中对 对象本身进行操作的API迁移到了Reflect上,并且又新增了一些别的方法

Reflect和Object的区别

  • 早期的ECMA规范中没有考虑这中对 对象本身的操作如何设计会更加规范,所以将API放到了Object上
  • 但是Object作为一个构造函数这些操作实际放到它身上并不合适
  • 另外还包含一些类似于in、delete操作符让js看起来会有一些奇怪
  • 所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上
  • 另外在使用proxy时,可以做到不操作原对象
const obj = {
    name: "zs",
    age: 19,
};
// 返回 对象本身
console.log(
    Object.defineProperty(obj, "name", {
        configurable: false,
    })
);
// 返回Boolean值  false上面设置了configurable:false
console.log(
    Reflect.defineProperty(obj, "name", {
        enumerable: false,
    })
);

// 都返回Boolean 但是后者的书写的语义更加明确
console.log(delete obj.name); // false
console.log(Reflect.deleteProperty(obj, "name"));  // false

Reflect和Proxy的搭配使用

  • 好处一:
    • 代理对象的目的:不直接操作原对象
  • 好处二:
    • Reflect.set方法返回Boolean值,可以判断本次的操作是否成功
  • 好处三:
    • 见下面案例好处三
const obj = {
    name: "zs",
    age: 19,
};

Reflect.defineProperty(obj, "name", {
    configurable: false,
    writable: false,
});
// 使用Reflect对对象进行操作,可以进行间接操作对象
// 可以返回一个Boolean值
// 如果对象的属性被保护起来了,那么可以判断操作是否成功
// 避免了严格模式下报错
const proxyObj = new Proxy(obj, {
    set(target, key, newVal, receiver) {
        let isConfig = Reflect.set(target, key, newVal, receiver);
        // let isConfig = target[key];
        console.log(isConfig);
        if (!isConfig) {
            console.log(`${key}属性不支持该操作`);
        }
    },
});
proxyObj.name = "河南";
console.log(proxyObj);

案例好处三

const obj = {
    _name: "zs",
    set name(newVal) {
        this._name = newVal;
    },
    get name() {
        return this._name;
    },
};

//  Reflect的receiver参数表示
//  set/get(obj中的set/get) 方法执行的时候的this

// 如果没有传入receiver,那么obj中的set在调用的时候只监听了一次
// 当调用proxyObj.name的时候其实是调用了两次set方法
// 第一次是调用了proxyObj的set,然后执行了Reflect.set()
// 调用了obj的set方法,但是此时obj的set中的this指向的是obj
// 操作源对象,proxyObj无法监听到,所以只监听了一次

// 如果传入receiver,
// 当执行Reflect.set(),时候,obj的this已经变成了proxyObj这个代理对象
// this._name其实就是proxyObj._name
// 那么又会执行proxyObj的set方法,所以就可以监听两次

// get方法和上述同理

// 如果不使用Reflect的方法操作对象,那么也只会监听一次,因为obj的this就是指向obj本身

// 所以 如果想要监听两次操作,就使用Reflect,如果不在乎的话,也可以使用target[key]

let proxyObj = new Proxy(obj, {
    set(target, key, newVal, receiver) {
        console.log("设置了值");
        Reflect.set(target, key, newVal, receiver);
    },
    get(target, key, receiver) {
        console.log("获取了值");
        return Reflect.get(target, key, receiver);
    },
});

proxyObj.name = "123";
proxyObj.name;

10.Promise

  • promise是一个类
  • 在通过new创建Promise对象时,传入一个回调函数,我们称之为executor
    • 这个回调函数会被立即执行 ,并且给传入两个回调函数resolve,reject
    • 调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数
    • 调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数

10.1.promise的状态

  • pending(待定)
    • 初始状态,既没有被兑现,也没有被拒绝,
  • fulfilled(已对象)
    • 意味着操作成功,执行了resolve时,处于该状态
  • rejected(已拒绝)
    • 意味着操作失败,执行reject时,处于该状态

10.2.注意

  • Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数(Executor是传入的回调的叫法)
  • Promise的状态一旦被确定下来,就不会更改,也不能在执行某一个回调函数来改变状态
    • 一旦调用了resolve或者reject,就不能在调用这两个中的任意一个,调用了也没有反应

10.3.resolve的值

  • 如果传入一个普通的值或者对象,那么这个值将作为then方法的回调的参数
  • 如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原来的Promise的状态
  • 如果resolve中传入的是一个对象,并且这个对象中有then方法,那么会执行then方法,并根据then方法的结果来决定promise的状态
const p = new Promise((resolve, reject) => {
    resolve("p的resolve");
});
const promise = new Promise((resolve, reject) => {
    // 1. resolve() 普通的值
    // resolve(123);

    // 2. resolve() 一个promise, 这个新的promise会决定原promise的状态
    // resolve(p);

    // 3. resolve() 一个带有then方法的对象
    resolve({
        name: "22",
        then(resolve, reject) {
            // resolve("hhh");
            reject("ss");
        },
    });
});

promise
    .then((val) => console.log(`结果:${val}`))
    .catch((err) => console.error(err));

10.4.then方法相关

  • 可以调用多次then方法,里面的回调都会被调用
  • 可以将catch写在then方法中
  • 写then的时候后面最好跟一个catch方法,不然如果promise中拒绝了,那么外面就会报错
const promise = new Promise((reslove, reject) => {
    reslove('成功的回调')
    // reject('失败的回调')
})

promise.then(() => {})
promise.then(() => {})

promise.then(res => {}, err => {})


10.4.1.then方法支持链式调用

  • then方法的返回值也是一个Promise,所以可以支持链式调用
// then方法返回一个新的promise,这个新的promise的决议(resolve/reject)是等到then方法传入的回调函数有返回值时进行决议的
const promise = new Promise((resolve, reject) => {
    resolve("aaa");
    // reject("ss");
});

// then方法中的返回值的类型(见上面的10.3)
// 普通值
// 新的Promise
// thenable对象(对象中有then方法)
promise
    .then((res) => {
    console.log(res); // aaa
    return "hhh";
    // return {
    //   then(resolve, reject) {
    //     reject(0.11);
    //   },
    // };
})
    .then((res) => {
    console.log(res); // hhh
});

// then方法相当于做了以下的操作
// 所以如果then方法中没有返回值,那么下个then方法中的结果就是undefined
function then(cb) {
    let res = cb();
    return new Promise((resolve, reject) => {
        resolve(res);
    });
}

10.5.catch方法相关

  • 可以调用多次catch方法,里面的回调都会被调用
const promise = new Promise((reslove, reject) => {
    reject('失败的回调')
    // reject('失败的回调')
})

promise.reject(() => {})
promise.reject(() => {})

10.5.1.catch的链式调用

  • then方法的返回值也是一个Promise,所以可以支持链式调用
const promise = new Promise((resolve, reject) => {
    reject("ee");
});

// catch方法也会返回一个新的Promise
// catch方法默认返回undefined
// 如果写了return 或者默认的返回,那么决议就是resolve,链式调用的下一级就会进入到then方法中
// 所以默认情况下不能一直.catch().catch()调用
// 如果想使用.catch.catch,那么可以在每个catch中抛出一个异常,这样就可以一直被catch所捕获
promise
    .catch((err) => console.log(err)) //ee
    .then((res) => console.log(res)) // undefined
    .then((res) => console.log(res)); // undefined

const promise1 = new Promise((resolve, reject) => {
    resolve("ee");
});

// 如果有promise的决议是reject的,那么会找到最近的catch方法进行调用
// 所以下面代码会在执行第一个then后,直接执行catch方法
// 使用throw 或者 return一个新的promise(决议是reject)都会进入catch中
promise1
    .then((res) => {
    console.log(res, "asa");
    throw new Error("报错");
})
    .then((res) => console.log(res))
    .catch((err) => console.log(err));

// catch方法相当于做了以下的操作
function catch1(cb) {
    let res = cb();
    return new Promise((resolve, reject) => {
        resolve(res);
    });
}

10.6.finally方法相关

  • es9新增的新特性
  • 表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的代码
  • finally方法不接受参数,因为无论前面是fulfilled还是rejected状态,他都会执行

10.7.Promise的类方法

resolve方法

  • 就是new promise(resolve => resolve())的语法糖

reject方法

  • 就是new promise((resolve,reject) => reject())的语法糖

all方法

  • 他的作用是将多个Promise包裹在一起形成一个新的Promise
  • 新的Promise状态由包裹的所有Promise共同决定
    • 所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有的Promise的返回值组成一个新的数组
    • 有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数,给catch方法
const promise = new Promise((reslove, reject) => {
    setTimeout(() => {
        reslove("11");
    }, 3000);
});
const promise1 = new Promise((reslove, reject) => {
    setTimeout(() => {
        reslove("22");
    }, 2000);
});
const promise2 = new Promise((reslove, reject) => {
    setTimeout(() => {
        reslove("33");
    }, 1000);
});

// 如果上面的三个promise中有一个reject,那么不用等到全部promise执行完毕,直接调用catch,剩余没有执行的promise也不会执行了
Promise.all([promise, promise1, promise2])
    .then((res) => {
    console.log(res); //["11", "22", "33"];
})
    .catch((err) => console.log(err));

allSettled

  • es11中新增的方法
  • 该方法会在所有的Promise都有结果(settled),(无论是fulfilled还是rejected),才会有最终的状态
  • 并且这个Promise的结果一定是fulfilled状态的
const promise = new Promise((reslove, reject) => {
    setTimeout(() => {
        reject("11");
    }, 3000);
});
const promise1 = new Promise((reslove, reject) => {
    setTimeout(() => {
        reslove("22");
    }, 2000);
});
const promise2 = new Promise((reslove, reject) => {
    setTimeout(() => {
        reslove("33");
    }, 1000);
});
Promise.allSettled([promise, promise1, promise2]).then((res) => {
    console.log(res);
});
// [
//   { status: "rejected", reason: "11" },
//   { status: "fulfilled", value: "22" },
//   { status: "fulfilled", value: "33" },
// ];

race方法

  • race是竞赛、竞技的意思
  • 表示多个Promise,谁先有结果那么就使用谁的结果
  • 无论Promise的状态是fulfilled还是rejected
const promise = new Promise((reslove, reject) => {
    setTimeout(() => {
        reject("11");
    }, 300);
});
const promise1 = new Promise((reslove, reject) => {
    setTimeout(() => {
        reslove("22");
    }, 2000);
});
const promise2 = new Promise((reslove, reject) => {
    setTimeout(() => {
        reslove("33");
    }, 1000);
});
Promise.race([promise, promise1, promise2])
    .then((res) => {
    console.log(res);
})
    .catch((err) => console.log(err));
// 11

any方法

  • es12新增的方法
  • any方法等到一个fulfilled状态,才会决定新Promise的状态
    • 如果有一个Promise是fulfilled,那么直接调用then方法,只会将当前的内容传进去
  • 如果所有的Promise都是rejected,那么也会等到所有的Promise都变成rejected状态,然后会进入catch方法,会报一个AggregateError的错误
const promise = new Promise((reslove, reject) => {
    setTimeout(() => {
        reject("11");
    }, 300);
});
const promise1 = new Promise((reslove, reject) => {
    setTimeout(() => {
        reject("22");
    }, 2000);
});
const promise2 = new Promise((reslove, reject) => {
    setTimeout(() => {
        reject("33");
    }, 1000);
});
Promise.any([promise, promise1, promise2])
    .then((res) => {
    console.log(res);
})
    .catch((err) => console.log(err));
// AggregateError: All promises were rejected

11.Iterator(迭代器)

  • 在Javascript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol)
    • 迭代器协议定义了产生一系列值(无论是有限还是无线个)的标准方式
    • 在JavaScript中这个标准就是一个特点的next方法

next方法有如下的要求

  • 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象
  • done(Boolean)
    • 如果迭代器可以产生序列中的下一个值,则为false
    • 如果迭代器已将序列迭代完毕,则为true,这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值(一般情况是undefined)
  • value
    • 迭代器返回的任何JavaScript值。done为true时可以省略
// namesIterator对象就是names的迭代器
const names = ["a", "b", "c"];
let i = 0;
const namesIterator = {
    next() {
        if (i < names.length) {
            return { done: false, value: names[i++] };
        } else {
            return { done: true, value: names[i++] };
        }
    },
};
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

11.1.可迭代对象

  • 当一个对象实现了iterable protocol协议时,他就是一个可迭代对象
  • 这个对象的要求是必须实现@@iterator方法,在代码中使用Symbol.iterator访问该属性
  • 如果想让一个对象变成可迭代的对象,那么需要在这个对象中返回一个迭代器
  • 这个对象中必须有属性名是[Symbol.iterator]方法
    • 方法名 是 Symbol.iterator 这个是Symbol类的方法所以要用计算属性名
    • 这个方法返回一个迭代器
const infos = {
    name: "jack",
    age: 19,
    height: 1.88,
    [Symbol.iterator]() {
        // 获取key
        // let keys = Object.keys(this);
        // 获取值
        // let values = Object.values(this);
        // 获取entries(键值对)
        let entries = Object.entries(this);
        let index = 0;

        let iterator = {
            next: () => {
                if (index < entries.length) {
                    return { done: false, value: entries[index++] };
                } else {
                    return { done: true, value: undefined };
                }
            },
        };
        return iterator;
    },
};

for (let info of infos) {
    console.log(info);
}

11.2.可迭代对象的应用

JavaScript中语法(常用)

  • for…of
  • 展开语法(spread syntax)
  • yield*
  • 解构赋值(Destructuring_assignment)

创建一些对象时:

  • new Map([iterator]) / new WeakMap([iterator])
  • new Set([iterator]) / new WeakSet([iterator])

一些方法的调用

  • Promise.all(iterator)
  • Promise.race(iterator)
  • Array.from(iterator)

11.3.自定义类的迭代

class Person {
    constructor(name, age, height, friends) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.friends = friends;
    }

    [Symbol.iterator]() {
        let index = 0;
        let values = Object.values(this);
        return {
            next() {
                if (index < values.length) {
                    return { done: false, value: values[index++] };
                } else {
                    return { done: true, value: values[index++] };
                }
            },
        };
    }
}

const p1 = new Person("jack", 18, 1.88, ["rose", "lily"]);

for (let item of p1) {
    console.log(item);
}

11.4.终端检测器

  • 迭代器在某些情况下没有完全迭代的情况下终端
    • 比如遍历的过程中通过break、return、throw中断了循环操作
    • 比如在解构时候,没有解构所有的值
  • 就是迭代器的return方法
const infos = {
    name: "jack",
    age: 19,
    height: 1.88,
    [Symbol.iterator]() {
        // 获取值
        let values = Object.values(this);
        let index = 0;

        let iterator = {
            next: () => {
                if (index < values.length) {
                    return { done: false, value: values[index++] };
                } else {
                    return { done: true, value: undefined };
                }
            },
            return() {
                console.log("进入终端监测");
                return { done: true };
            },
        };
        return iterator;
    },
};
const [name] = infos;
// console.log(name);

12.Generator(生成器)

12.1.什么是生成器

  • 生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行

  • 生成器由生成器函数所创造的

  • 生成器函数也是函数,但是和普通函数有些区别

    • 首先,生成器函数需要在function后面加一个符号 *

      • 符号挨着function后面,或者挨着函数名前面都行

      • 如果没有function关键字声明的函数,*直接加在名字前面

      • let info = {
            *next() {}
        }
        
        
    • 其次,生成器函数可以通过yield关键字来控制函数的执行流程

    • 最后,生成器函数返回值是一个Gererator(生成器)

      • 生成器事实上是一种特殊的迭代器
function* foo() {
    console.log("first");
    console.log("first");
    yield;
    console.log("first`q");
    console.log("first`q");
}

const gererator = foo();

12.2.生成器函数执行

  • 生成器函数的代码直接调用是不会执行

    • 因为生成器函数返回一个生成器
  • 通过调用返回的生成器的next方法来执行生成器函数中的代码块

  • 当执行代码的过程中遇到yield关键字的时候会暂停执行

    • next函数是有返回值的
  • 如果想继续执行,需要再次调用next函数

  • yield 后面可以跟值

    • 这个值就会作为next返回值中对象的value的值
  • 调用next的时候可以传参,但是这个参数会在yield之后的代码中接收到

// next函数传参 第一次执行next无法接收到,一般直接传在函数中
function* foo(res) {
    console.log("111", res);
    console.log("111", res);
    const res1 = yield "萘萘萘";
    // return "萘萘萘";
    console.log("222", res1);
    console.log("222", res1);
    const res2 = yield "哈哈哈";
    console.log("333", res2);
    console.log("333", res2);
}

const gererator = foo("第一个");
console.log(gererator.next());
console.log(gererator.next("第二个"));
console.log(gererator.next("第三个"));

// 执行结果如下
// {value: '萘萘萘', done: false} 这种是next函数的返回值
// 如果遇到return后续代码不会执行,next的返回值是 {value: '萘萘萘', done: true}
// 之后在执行next 返回值都是 {value: undefined, done: true}
/*
        111 第一个
        111 第一个
        {value: '萘萘萘', done: false}
        222 第二个
        222 第二个
        {value: '哈哈哈', done: false}
        333 第三个
        333 第三个
         {value: undefined, done: true}
      */

12.3.生成器函数提前结束

  • 除了在函数中直接使用return,也可以用下面的方式
function* foo() {
    try {
        console.log("1111");
        const c1 = yield "aaa";
        console.log("2222", c1);
        const c2 = yield "bbb";
        console.log("3333", c2);
    } catch (error) {
        console.log(error);
    }
}

使用return函数

  • return传值后这个生成器函数就会结束,之后调用next不会继续生成值了

  • const genetarorFoo = foo();
        console.log(genetarorFoo.next("@@@"));
        console.log(genetarorFoo.return('###');
        console.log(genetarorFoo.next("$$$"));
    
    

使用throw函数

  • 抛出异常后可以在生成器函数中捕获异常

  • 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行

  • const genetarorFoo = foo();
    console.log(genetarorFoo.next("@@@"));
    console.log(genetarorFoo.throw(new Error()));
    console.log(genetarorFoo.next("$$$"));
    
    

13.生成器替代迭代器

  • 生成器本身就是特殊的迭代器
  • 生成器函数返回的是一个生成器,可以调用next方法
  • 当遇到yield时候,返回值的done是false
  • 没有yield时候,返回值的done是true

13.1.生成器代替迭代器应用场景

const names = ['jack', 'rose', 'lily']

function* createIterator(iter) {
for (let i = 0; i < iter.length; i++) {
yield iter[i]
}
// yield iter[0]
// yield iter[1]
// yield iter[2]
}

const namesIterator = createIterator(names)

console.log(namesIterator.next()) // {value: 'jack', done: false}
console.log(namesIterator.next()) // {value: 'rose', done: false}
console.log(namesIterator.next()) // {value: 'lily', done: false}
console.log(namesIterator.next()) // {value: undefined, done: true}


可迭代对象的优化版本

  • 可迭代对象的标准
    • [Symbol.iterator]方法中返回一个迭代器
  • 生成器本身就是一个特殊的迭代器
const infos = {
    firends: ["jack", "rose", "lily"],
    *[Symbol.iterator]() {
       yield* this.firends;
    },
};

for (let info of infos) {
    console.log(info);
}

13.2.yield* 的使用

  • yield* 可以产生一个可迭代对象
  • yield* 后面跟的必须是一个可迭代的对象
  • yield*相当于是一种yield的语法糖,他会依次迭代后面跟着的可迭代对象的内容
const names = ['jack', 'rose', 'lily']
yield* names

相当于

yield 'jack'
yield 'rose'
yield 'lily'


// 简化代码
function* createIterator(arr) {
    yield* arr
}

14.async\await

14.1.异步函数

  • async function
  • async是asynchronous的缩写,表示异步的

14.2.异步函数的执行流程

  • 异步函数的内部代码执行过程和普通函数执行是一致的,默认情况下也会被同步执行
  • 异步函数有返回值时,和普通函数是有区别的
    • 情况一:异步函数也是有返回值的,但是异步函数的返回值相当于被包裹到Promise.resolve中
      • 没有返回值时,默认返回undefined
    • 情况二:如果异步函数的返回值是Promise,那么这个返回值的状态由Promise决定
    • 情况三:如果异步函数的返回值是一个对象,并且实现了thenable,那么状态会由对象的then方法来决定
  • 如果在async中出现了异常,那么程序并不会想普通函数一样报错,而是会作为Promise.reject来传递
 async function foo() {
        a.map(ss); // 报错 会在catch中被捕获
        return "aaa";
      }

      // 代码中的报错会在catch中执行
      foo()
        .then((res) => console.log(res))
        .catch((err) => console.log(err));

14.3.await关键字

  • async函数另外一个特殊之处就是可以在他内部使用await关键字,而普通函数是不可以的

  • await关键字的特点

    • 通常使用await后面会跟上一个表达式,这个表达式会返回一个Promise
    • await会等到Promise的状态变为fulfilled状态,之后继续执行异步函数
  • 如果await后面是一个普通值,那么会直接返回这个值

    • 如果该值不是一个 Promise,await 会把该值转换为已正常处理的 Promise,然后等待其处理结果。

    • async function foo() {
          await 20;
          //相当于 await Promise.resolve(20)
      }
      
      
  • 如果await后面是一个thenable对象,那么会根据thenable的then方法来调用后续的值

  • 如果await后面的表达式,返回的Pomise是rejected的状态,那么会将这个reject结果直接作为Promise的reject值

15.事件循环

面试题相关的知识储备

15.1.进程和线程

  • 进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式
  • 线程(thred):操作系统能够运行运算调度的最小单位,通常情况下他被包含在进程中

15.2.浏览器中的JavaScript线程

  • JavaScript是单线程的

浏览器的进程

  • 目前多数的浏览器都是多进程的,当我们打开一个标签页就会开启一个新的进程,这样做的目的是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出
  • 每个进程又有很多线程,其中包括执行JavaScript代码的线程(js是单线程的)

JavaScript的代码是在一个单独的线程中执行的

  • 这就意味着JavaScript的代码同一时刻只能做一件事
  • 如果这件事是非常耗时的,就意味着当前的线程会被阻塞

耗时的操作不是由JavaScript线程执行的

  • 浏览器每个进程都是多线程的,那么其余的线程可以来做这个耗时的操作
  • 比如网络请求、定时器、我们只需要在特定的时候执行应该有的回调即可

15.3.浏览器的事件循环

  • 当代码执行的时候如果遇到了异步的操作,浏览器会将这些异步操作交给处理异步操作的线程
  • 等到这些异步操作有结果了,异步操作的线程会将当前异步操作的回调添加到事件队列里面
  • 等到执行上下文调用栈中的执行上下文全部执行完毕后(调用栈空了),去任务队列中寻找任务,将任务队列中的任务添加到执行上下文调用栈中执行
  • 此过程如果又有异步操作,那么继续重复上面的操作,此操作就称为事件循环
  • DOM监听、XMLHttpRequest、定时器都会被放入到事件队列中
    • 浏览器会监听Dom事件的,然后将其放入到任务队列中
    • 队列是先进先出的
  • 注意:
  • setTimeout(cb,1000)
    • setTimeout是一个函数,里面有一个cb回调
    • 程序执行到setTimeout时,会在调用栈中创建一个函数执行上下文,
    • 然后将cb回调交给执行异步操作的线程
    • 然后setTimeout函数执行上下文弹出栈
    • 等到1000ms后,将其加到任务队列中

15.4.宏任务和微任务

事件循环中并非只维护着一个队列,事实上有两个队列

  • 宏任务队列(macrotask queue):Ajax、setTimeout、setInterval、DOM监听、UI Rendering等
  • 微任务队列(microtask queue):Promise的then回调/catch回调、Mutilation Obrserver API、queueMicrotask等

两个队列的优先级

  • main script中的代码优先执行(编写的顶层script代码)
  • 执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
    • 也就是宏任务执行前,必须保证微任务队列是空的
    • 如果不为空,那么久优先执行微任务队列中的任务(回调)

15.5.面试题

Promise面试题

console.log("script start");

setTimeout(() => {
    console.log("setTimeout1");
    new Promise(function (resolve) {
        resolve();
    }).then(function () {
        console.log("then4");
    });
    console.log("then2");
});

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("then1");
});

setTimeout(() => {
    console.log("setTimeout2");
});

console.log(2);

queueMicrotask(() => {
    console.log("queueMicrotask1");
});

new Promise(function (resolve) {
    resolve();
}).then(function () {
    console.log("then3");
});

console.log("script end");

// script start
// promise1
// 2
// script end
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2

/* 解析
        1. main script代码执行
        2. 打印script start
        3. 遇到第一个setTimeout,将其放入宏任务队列
        4. 遇到第一个new Promise 打印promise1,resolve()时 将then回调放入微任务队列
        5. 遇到第二个setTimeout, 将其放入宏任务队列
        5. 打印2
        6. 遇到queueMicrotask,将回调放入微任务队列
        7. 遇到第二个new Promise,resolve()时 将then回调放入微任务队列
        8. 打印script end
        9. 当前调用栈的代码执行完毕,去任务队列中找,根据浏览器在执行宏任务队列之前,会先清空微任务队列的机制,(队列是先进先出的数据结构)
        10. 依次打印 then4  queueMicrotask1  then3
        11. 执行宏任务的第一个回调 打印setTimeout1
        12. 遇到一个new Promise resolve()时 将then的回调放入微任务队列
        13. 打印then2
        14. 当前宏任务执行完毕,执行下一个宏任务,监测微任务队列是否有任务
        15. 打印then4(刚刚推入微任务的回调)
        16. 执行宏任务队列的任务  打印setTimeout2
      */

async、await面试题

  • async函数如果没有返回值,默认返回undefined
    • 相当于Promise.resolve(undefined)
  • await 后面通常跟一个返回值是Promise的表达式,await会等到Promise的状态是fulfilled时,才执行后续代码
  • 如果Promise的状态是fulfilled,那么Promis后面的代码相当于被包裹在then函数中,所以后续代码会被放入到微任务队列中
async function async1() {
    console.log("async1 start");
    await async2();
    console.log("async end");
}

async function async2() {
    console.log("async2");
}

console.log("script start");

setTimeout(() => {
    console.log("setTimeput");
}, 0);

async1();

new Promise((resolve) => {
    console.log("promise1");
    resolve();
}).then(() => {
    console.log("promise2");
});

console.log("script end");

// script start
// async1 start
// async2
// promise1
// script end
// async end
// promise2
// setTimeput
/*
        1. main script执行
        2. 打印script start
        3. 执行setTimeout,将其给执行异步操作的线程,当时间结束后,异步操作的线程将其放入宏任务队列
        4. 执行async1()
        5. 打印async1 start
        6. 执行 await async2()
        7. 打印 async2
        8. async2函数返回值相当于 Promise.resolve(undefined)
        9. async1中的await等到了fulfilled状态,将其后续代码加入到微任务中
        10. 执行new Promise 打印promise1
        11. 执行resolve() 将then代码推入微任务队列
        12. 打印script end
        13. 执行微任务队列代码 依次打印async end  promise2
        14. 执行宏任务队列任务, 打印setTimeput
      */

16.错误处理方案

16.1.抛出异常

  • 通过throw关键字,抛出一个异常
  • throw语句用于抛出一个用户自定义的异常
  • 当遇到throw语句时,当前函数执行会被停止(throw后面的语句不会被执行)

throw

  • throw后面可以跟上一个表达式来表示具体的异常信息
  • throw后面可以跟的类型
    • 基本数据类型: 比如number、string、boolean
    • 对象类型

Error类

  • JavaScript提供了一个Error类
  • Error包含三个属性
    • message: 创建Error对象时传入的message
    • name:Error的名称,通常和类的名称一致
    • stack:整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack
  • Error有一些自己的子类
    • RangeError:下标值越界时使用的错误类型
    • SyntaxError:解析语法错误时使用的错误类型
    • TypeError:出现类型错误时,使用的错误类型
function foo() {
    // throw new RangeError("我是错误信息", "ss");
    // throw new TypeError("我是错误信息", "ss");
    throw new SyntaxError("我是错误信息", "ss");
}

foo();

16.2.异常的捕获

  • 当程序出现异常,这个异常会一层一层往上抛

  • 如果整个程序没有捕获异常,那么就会抛给浏览器,浏览器拿到异常会在控制台报错,后续代码就不会执行

  • 所以我们应该手动捕获异常

  • 使用try catch来捕获异常

  • catch后面可以跟finally

  • es10中catch后面绑定的error可以省略

    • try{
          
      }catch{
          //不用error的信息,可以省略
      }finally {
           console.log("无论是否异常都会执行");
      }
      
      
function foo() {
    // throw new RangeError("我是错误信息", "ss");
    // throw new TypeError("我是错误信息", "ss");
    throw new SyntaxError("我是错误信息", "ss");
}
try {
    foo();
} catch (error) {
    console.log(error.name); //SyntaxError
    console.log(error.message); // 我是错误信息
    console.log(error.stack);
    // SyntaxError: 我是错误信息
    // at foo (异常处理.html:14:15)
    // at 异常处理.html:17:9
}

17.WebStorage

17.1.认识Storage

  • localStorage:本地存储,提供的是一种永久性的存储方法,在关掉网页重新打开时,存储内容依然保留
  • sessionStorage:会话存储,提供本次会话的存储,在关掉会话(网页)时,存储的内容会被清除

17.2.两者区别

  • 关闭网页重新打开,localStorage保存,sessionStorage不保存
  • 同一页面内实现跳转,localStorage和sessionStorage都会被保存
  • 跳转到不同的网页时(打开一个新的页面),localStorage保存,sessionStorage不保存

17.3.Storage的常见属性

  • length
    • 只读属性,返回Storage中对象的个数
  • setItem、getItem
  • key(index)
    • 返回储存在第index的key名称
  • removeItem
  • clear
localStorage.key(0)
sessionStorage.key(0)

18.正则表达式

定义

  • 正则表达式(Regular Expression。简写为regex、regexp、RE)
  • 则这表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串
  • 简单来说:正则表达式是一种字符串匹配利器,可以帮助我们搜索、获取、替代字符串

使用

  • JavaScript中,正则表达式使用RegExp类来创建,也有对应的字面量方式

  • 正则表达式主要由两部分构成:模式(patterns)和修饰符(flags)

  • const reg = new RegExp('aa','ig')
    const reg1 = /aa/i
    
    

RegExp实例方法

  • test(str)
    • 执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 truefalse
  • exec(str)
    • 在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null

字符串中的方法(可以使用正则)(部分方法)

  • match
    • 检索返回一个字符串匹配正则表达式的结果。
  • matchAll
    • matchAll的正则必须全局匹配,加g
    • 返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
  • replace
  • replaceAll
  • split
  • search

常见的修饰符

  • g
    • 全部的,匹配全部的(global)
  • i
    • 忽略大小写(ignore)
  • m
    • 多行匹配(multiple)

18.1.RegExp中的规则

18.1.1 字符类

字符类(character classes)是一个特殊的符号,匹配特定集中的任何符号

下面每个符号只能匹配一个

  • \d (digit)
    • 表示0~9的数字
  • \s (space)
    • 表示空格符号,包括(制表符\t、换行符\n和其他稀少的字符)
  • \w (word)
    • 表示拉丁字母、数字、下划线
  • .
    • 表示除换行符之外的任何字符

反向类(inverse classes)

  • \D
    • 表示非数字,除\d以外的任何字符
  • \S
    • 表示非空格,除\s之外的任何字符
  • \W
    • 表示非单位字符,除\w之外的任何字符

18.1.2锚点

符号^和符号$在正则中具有特殊意义,称之为锚点

  • ^ 表示匹配开头
  • $ 表示匹配结尾

词边界

  • 词边界\b是一种检查,像^和$一样,他会检查字符串中的位置E是否是词边界
  • 词边界测试\b检查位置的一侧是否匹配\w,而另一侧则不匹配\w
//  表示匹配紧挨着name左边的不可以是\w相关的(字母、数字、下划线)这之外其他的都行,
//右边则可以是任意
// 还可以这样 
/\bname\b/  // 表示name两侧紧挨着的不能是\w相关的

const str = "my 我namea is zs";
if (/\bname/gi.test(str)) {
    console.log("first");  
}
// first

//案列  取出时间
const info = `time 12:00 eat some food 234:88`;
const timeRe = /\b\d\d:\d\d\b/g;
console.log(info.match(timeRe));// [12:00]

18.1.3.集合和范围

集合

  • 使用[]
  • 比如说,[eao]意味着在这三个字符e、a、o中任意一个都行

范围

  • 方括号也可以包含字符范围
  • 比如说[a-z]会匹配从a到z范围内的字母
  • \d 等同于[0-9]
  • \w 等同于[a-zA-Z0-9_]

排除范围

  • 中括号内开头添加^
  • [^0-9] 表示排除0-9

18.1.4.量词(quantifiers)

  • 用来形容我们所需要的数量的词称为量词
  • 数量{n}
  • 确切的位数:{5}
  • 某个范围的位数: {3,5}
  • 某个数字到任意: {2,}
/a{3,5}/
// 表示匹配3到5直接数量的a

量词的缩写

    • 表示一个或多个,同{1,}
    • 表示0个或多个,同{0,}
  • ?
    • 表示0个或1个,同{0,1}

18.1.5.贪婪模式和惰性模式

  • 默认情况下的匹配规则是查找到匹配的内容后,会继续向后查找,一直找到最后一个匹配的内容
    • 这种匹配模式,称之为贪婪模式(Greedy)
  • 懒惰模式中的量词与贪婪模式中是相反的
    • 只要获取对应的内容后,就不再继续向后匹配
    • 可以在量词后面加一个?来启用
    • 所以匹配模式应变成**+?** *? ??
  • 默认情况下匹配规则是查找到匹配内容后,会继续向后查找,一直找到最后一个匹配的内容
    • 这种匹配方式,称之为贪婪模式(Greedy)
  • 懒惰模式中的量词与贪婪模式中的是相反的

// 获取书名
const message = "我最喜欢的一本书是《肖申克的救赎》和《小猪佩奇》";
//默认情况 .+ 是贪婪模式
// 获取的结果就是["《肖申克的救赎》和《小猪佩奇》"]
// 因为 》也是 .能够匹配到的
// const bookRe = /《.+》/g;

// 在后面加?表示开启惰性模式
// 表示找到一个符合的就将结果储存,然后继续后续查找
const bookRe = /《.+?》/g;
console.log(message.match(bookRe)); // ['《肖申克的救赎》', '《小猪佩奇》']

18.1.6.捕获组(capturing group)

  • 模式的一部分可以用括号括起来,这称为捕获组
  • 有两个作用
    • 允许将匹配的一部分作为结果数组中的单独项
    • 将括号视为一个整体
      const message = "我最喜欢的一本书是《肖申克的救赎》和《小猪佩奇》";
	// 匹配结果没有加g 也可用matchAll
      const bookRe = /《(.+?)》/;
      console.log(message.match(bookRe)); 
      // 索引0 "《肖申克的救赎》"
      // 索引1 "肖申克的救赎"

命名组

  • 给捕获组添加名称(用数字记录组很困难),这样使用类似match和matchAll中就可以在group中获取了
  • 在括号开始的位置放置?<name>
const reg = /(?<hhh>hel)lo/i

19.防抖和节流

  • JavaScript是事件驱动的,大量的操作会出发事件,加入到事件队列中处理
  • 对于某些频繁的事件处理会造成性能的损耗,就可以通过防抖和节流来限制事件的频繁发生
  • 防抖和节流都是函数

19.1.防抖函数

  • 有一个输入框,监听这个输入框的input事件
  • 用户想搜索macbook,但是只要一输入就会发送网络请求,这显然是消耗内存的
  • 那么就可以用防抖来解决
    • 当用户输入的时候如果连续输入就不发送请求,
    • 如果间隔(比如说间隔200ms)内没有输入,那么就发送一次请求
// 基本实现 -取消防抖、是否立即执行
function ymDebounce(cb, delay, immediate = false) {
    // 创建变量
    let timer = null;
    let isInvoke = false;
    // 返回一个函数用来作为oninput的事件
    const _debounce = function (...args) {
        // 判断是否立即执行
        if (immediate && !isInvoke) {
            cb.apply(this, args);
            isInvoke = true;
            return;
        }
        // 如果timer不是空 那么就清空计时器
        // 如果delay时间内再次调用此函数那么上一次的计时器就会被清空
        // 这就是防抖的核心代码
        if (timer) clearTimeout(timer);
        // 创建一个计时器
        timer = setTimeout(() => {
            cb.apply(this, args);
            // 执行过后 将此次的计时器改为null
            // 不写也行,写了更加严谨,让一起回归原本
            timer = null;
            isInvoke = false;
        }, delay);
    };
    // 取消防抖
    _debounce.cancel = function () {
        if (timer) clearTimeout(timer);
        timer = null;
        isInvoke = false;
    };
    return _debounce;
}

// 有返回值
function ymDebounce(cb, delay, immediate = false) {
    // 创建变量
    let timer = null;
    let isInvoke = false;
    let res = undefined;
    // 返回一个函数用来作为oninput的事件
    const _debounce = function (...args) {
        return new Promise((resolve, reject) => {
            try {
                // 判断是否立即执行
                if (immediate && !isInvoke) {
                    res = cb.apply(this, args);
                    resolve(res);
                    isInvoke = true;
                    return;
                }

                if (timer) clearTimeout(timer);
                // 创建一个计时器
                timer = setTimeout(() => {
                    res = cb.apply(this, args);
                    resolve(res);
                    timer = null;
                    isInvoke = false;
                }, delay);
            } catch (error) {
                reject(error);
            }
        });
    };
    // 取消防抖
    _debounce.cancel = function () {
        if (timer) clearTimeout(timer);
        timer = null;
        isInvoke = false;
    };
    return _debounce;
}
const debounce = ymDebounce(
    (...args) => {
        console.log(args);
        return "eres";
    },
    1000,
    false
);
debounce("jack", 19, 1.88).then((res) => {
    console.log(res);
});

19.2.节流函数

  • 单位时间内,按照固定的频率触发事件
  • 比如有一个输入框,监听input事件
    • 如果1秒输入10次,那么就会调用10次请求
    • 现在想1秒内无论输入多少次只发一次请求,这就是节流
function ymThrottle(cb, interval, leading = true, trailing = false) {
    // startTime不用Date.now(),用0
    // 因为节流函数默认情况第一次是要触发的
    // 用0的话第一次触发awaitTime一定是<=0
    let startTime = 0;
    const _throttle = function (...args) {
        const nowTime = Date.now();
        // 判断是否立即执行
        // 让awaitTime 大于0即可
        if (!leading && startTime === 0) {
            startTime = nowTime;
        }
        // 这一行代码是核心代码
        // nowTime - startTime 表示代码执行开始到执行中任意时间的间隔
        // interval - (nowTime - startTime) 表示用户传入的时间间隔减去执行的时间间隔
        // 如果这个差值小于等于0的话就执行函数
        const awaitTime = interval - (nowTime - startTime);
        //
        if (awaitTime <= 0) {
            cb.apply(this, args);
            // 将开始时间重新赋值,目的是计算下次的时间间隔
            startTime = Date.now();
        }
    };
    return _throttle;
}

// 实现尾部控制相关-取消和返回值
function ymThrottle(
cb,
 interval,
 { leading = true, traling = false } = {}
) {
    let startTime = 0;
    let timer = null;
    const _throttle = function (...args) {
        return new Promise((resolve, reject) => {
            try {
                const nowTime = Date.now();

                if (!leading && startTime === 0) {
                    startTime = nowTime;
                }

                const waitTime = interval - (nowTime - startTime);

                if (waitTime <= 0) {
                    console.log("first");
                    if (timer) clearTimeout(timer);
                    const res = cb.apply(this, args);
                    resolve(res);
                    startTime = nowTime;
                    timer = null;
                    return;
                }
                // 实现尾部取消
                // 在间隔点之后添加一个定时器
                // 如果是间隔点那么就会取消这个定时器
                if (traling && !timer) {
                    timer = setTimeout(() => {
                        console.log("timer");
                        const res = cb.apply(this, args);
                        resolve(res);
                        startTime = Date.now();
                        timer = null;
                    }, waitTime);
                }
            } catch (error) {
                reject(error);
            }
        });
    };
    // 取消
    _throttle.cancel = function () {
        if (timer) clearTimeout(timer);
    };
    return _throttle;
}

20.深拷贝&&事件总线

20.1.深拷贝

// 要用weakMap 用完后没有对newObj进行强引用,newObj会被垃圾回收

function deepCopy(originValue, map = new WeakMap()) {
    // 如果是symbol类型
    if (typeof originValue === "symbol")
        return Symbol(originValue.description);

    // 判断传入的是否是非对象
    if (!isObject(originValue)) return originValue;

    // 函数一般不会深拷贝,函数是用来执行的,深拷贝会造成占用内存
    if (originValue instanceof Function) return originValue;

    // 如果是set类型
    if (originValue instanceof Set) {
        const set = new Set();
        for (const item of originValue) {
            set.add(deepCopy(item));
        }
        return set;
    }

    // 循环引用
    if (map.get(originValue)) return map.get(originValue);

    // 判断是对象还是数组
    const newObj = Array.isArray(originValue) ? [] : {};

    // 循环引用
    map.set(originValue, newObj);

    for (const key in originValue) {
        newObj[key] = deepCopy(originValue[key], map);
    }
    // 如果key是symbol
    const symbolKeys = Object.getOwnPropertySymbols(originValue);
    for (const symbolKey of symbolKeys) {
        newObj[Symbol(symbolKey.description)] = deepCopy(
            originValue[symbolKey],
            map
        );
    }
    return newObj;
}

const s1 = Symbol("我是s1");
const info = {
    name: "zs",
    age: 19,
    address: {
        province: "HENAN",
        city: "信阳",
    },
    color: ["red", "blue", "green"],
};
// 循环引用
info.self = info;

const newInfo = deepCopy(info);
console.log(newInfo);

20.1.事件总线

class ymEventBus {
    constructor() {
        this.eventMap = {}
    }

    // 监听
    on(eventName, eventFn) {
        let eventFns = this.eventMap[eventName]

        if (!eventFns) {
            eventFns = []
            this.eventMap[eventName] = eventFns
        }

        eventFns.push(eventFn)
    }
    // 发射
    emit(eventName, ...args) {
        let eventFns = this.eventMap[eventName]
        if (!eventFns) return

        eventFns.forEach((fn) => fn(...args))
    }

    // 取消
    off(eventName, eventFn) {
        let eventFns = this.eventMap[eventName]
        if (!eventFns) return

        this.eventMap[eventName] = eventFns.filter((fn) => fn !== eventFn)

        if (!this.eventMap[eventName].length) {
            delete this.eventMap[eventName]
        }
    }
}

const bus = new ymEventBus()

const fn1 = (...args) => {
    console.log('vabClick 01', args)
}
const fn2 = (...args) => {
    console.log('vabClick 02', args)
}
bus.on('navClick', fn1)
bus.on('navClick', fn2)

const btnEl = document.querySelector('.nav-btn')
btnEl.onclick = function () {
    console.log('自己触发')
    bus.emit('navClick', '男', 19, 1.88)
    bus.off('navClick', fn1)
    console.log(bus)
}
console.log(bus)

21.XHR和Fetch

21.1.http相关

21.1.1.http版本

  • HTTP/0.9
    • 发布于1991年
    • 只支持GET请求方法获取文本数据,当时主要是为了获取HTML页面内容
  • HTTP/1.0
    • 发布于1996年
    • 支持POST、HEAD等请求方法,支持请求头、响应头等、支持更多种数据类型(不再局限文本数据)
    • 但是浏览器的每次请求都需要与服务器建立一个TCP连接,请求处理完成后立即断开TCP连接,每次建立连接增加性能损耗
  • HTTP/1.1(目前使用最广泛的版本)
    • 发布于1997年
    • 增加了PUT、DELETE等请求方法
    • 采用持久连接(Connection:keep-alive),多个请求可以共用一个TCP连接
  • HTTP/2.0
    • 2015年发布
  • HTTP/3.0
    • 2018年发布

21.1.2.Request Header

  • content-length
    • 我呢见打大小长度
  • keep-alive
    • http是基于TCP协议的,但是通常在进行一次请求和响应结束后会立刻中断
    • http1.0中需要手动开启
    • http1.1中默认开启
  • accept-encoding
    • 请求文件时用到
    • 告知服务器,客服端支持的文件压缩格式(浏览器自动配置)
    • 这样服务的就可以返回对应的压缩格式文件,节省传输时间
  • accept
    • 请求数据时用到
    • 告知服务器,客户端可接受文件的格式类型

21.1.3.response code

  • https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status

21.2.AJAX

XMLHttpRequest的state(状态)

状态描述
0UNSENT代理被创建,但尚未调用open()方法
1OPENEDopen()方法已被调用
2HEADRES_RECEIVEDsend()方法已被调用,并且头部和状态已经可获得
3LOADING下载中,responseText属性已经 包含部分数据
4DONE下载操作已完成

XMLHttpRequest的事件

  • onreadyStatechange
    • 监听readyState的变化
  • loadStart
    • 请求开始
  • progress
    • 一个响应数据包到达,此时整个response body都在response中
  • abort
    • 调用xhr.abort()取消了请求
  • error
    • 发生连接错误
  • load
    • 请求成功完成
  • timeout
    • 由于请求超时而取消了该请求(仅发生在设置了timeout的情况下)
  • loadend
    • 在load,error,timeout或abort之后触发

获取HTTP的响应状态

xhr.status
xhr.statusText

timeout&&abort

  • 在网络请求中,为了避免过长时间服务器无法返回数据,通常会为请求设置一个超时时间(timeout)
    • 当达到超时时间后依然没有获取到数据,那么这个请求会自动被取消掉
    • 默认值为0,表示没有设置超时时间
  • 也可以通过abort方法,强制取消请求

21.3.Fetch

  • 使用fetch上传文件时候,无法查看进度
  • Fetch可以看作是早期的XMLHttpRequest的替代方案,他提供了一种更加现代的处理方案
    • 比如返回值是一个Promise
    • 比如不像XMLHttpRequest一样,所有的操作都在一个对象上

fetch使用

  • fetch函数是浏览器提供的一个全局函数,可以直接使用
  • fetch函数的返回值是Promise
  • 拿到返回值后,如果返回的值是json类型,那么就调用json()方法
    • 这个返回值是一个 Response(类)对象
    • https://developer.mozilla.org/zh-CN/docs/Web/API/Response/json
    • 如果是其他的类型,就调用相应的方法
fetch('http://180.76.235.241:3000/media/list', {
    method: 'get',
    headers: {},
    body: {},
}).then(async (resp) => {
    const res = await resp.json()
    console.log(res)
})

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用idea工具实现新建java项目并连接mysql数据库

1.新建java项目 如果刚打开idea工具&#xff0c;则选择Create new Project。 在里新建使用File > New > Project 新建package包和class文件 右键src > new > package后输入包名 在刚建的包下右键选择 new > Java class后并输入类名 链接mysql Java 连…

win10系统下使用opencv-dnn部署yolov5模型

文章目录前言一、环境1、硬件2、软件二、YOLO模型三、新建Qt项目1、pro文件2、main函数四、YOLO 类封装1、yolo.h2、yolo.cpp3、classes.txt五、效果前言 最近工作中需要用yolov5训练模型&#xff0c;然后在win10系统下&#xff0c;完成推理部署。本篇主要介绍使用opencv-dnn模…

7判断环的入口结点8输出倒数第k个

7.判断环的入口结点 第一次第二次看感觉都无从下手&#xff0c;还是看了题解&#xff0c;主要思路是&#xff1a; 假设前面无环的结点有a个&#xff0c;环有b个结点&#xff0c;则设置快慢结点之后&#xff0c;fast结点一次走两步&#xff0c;slow结点一次走一步。则相遇时fast…

《第一行代码》核心知识点:活动(Activity)的儿子叫碎片(Fragment)

《第一行代码》核心知识点&#xff1a;活动Activity的儿子叫碎片Fragment前言四、活动(Activity)的儿子叫碎片&#xff08;Fragment&#xff09;4.1 碎片是神马&#xff1f;4.2 碎片的基本使用4.3 向容器中动态添加碎片4.4 活动与碎片之间通信方法4.5 碎片的生命周期4.6 使用限…

重载单目运算符以及重载运算符的注意事项

一、单目运算符的重载 单目运算符可分为两种&#xff1a; 1&#xff09;可以放在前面&#xff0c;也可以放在后面的单目&#xff0c;如&#xff1a; -- 2&#xff09;只能放在前面的运算符&#xff1a;&#xff01; &#xff08;正号&#xff09; -&#xff08;负号&#x…

Python函数详解(四)——Python函数参数使用注意事项

今天继续给大家介绍Python相关知识&#xff0c;本文主要内容是Python函数参数使用注意事项。 在上文Python函数详解&#xff08;三&#xff09;——函数的参数传递进阶中&#xff0c;我们学习了函数参数的进阶内容。今天&#xff0c;我们来学习函数的参数使用注意事项。 一、P…

【微信小程序】使用 Cryptiojs 解密微信绑定手机号码

很抱歉断更了一段时间&#xff0c;因为最近在做一个项目比较忙&#xff0c;正好项目中小程序板块需要解密手机号码来提交给接口&#xff0c;小程序中虽然提供了获取手机号按钮点击事件&#xff1a;bindgetphonenumber&#xff0c;但是该事件的处理函数中只能获取到加密过的手机…

关于如何调节Mahony AHRS算法的参数

文章目录一、Mahony算法的控制系统特征多项式二、Kp, Ki参数调节方法三、其他自适应调参法我在之前的博客AHRS互补滤波&#xff08;Mahony&#xff09;算法及开源代码中曾提及Mahony算法的难点在于如何调节PI参数。 最近看到参考文献[1]&#xff0c;提出了基于无阻尼自由频率设…

C# 将一种类型的数组转换为另一种类型的数组

将一种类型的数组转换为另一种类型的数组 public static TOutput[] ConvertAll<TInput, TOutput>(TInput[] array, Converter<TInput, TOutput> converter); 参数: array: 要转换为目标类型的从零开始的一维 System.Array。 converter: …

【数据结构与算法】杨辉三角,相同字符的截取以及扑克牌

✨个人主页&#xff1a;bit me ✨当前专栏&#xff1a;数据结构 ✨每日一语&#xff1a;不要等到了你的人生垂暮&#xff0c;才想起俯拾朝花&#xff0c;且行且珍惜。 ArrayList题训&#x1f335; 一. 杨辉三角&#x1f334;二.相同字符的截取&#x1f332;三.扑克牌&#x1f…

【SpringCloud】02 搭建springcloud微服务项目,服务治理组件nacos,负载均衡ribbon,远程调用Openfeign

文章目录搭建springcloud微服务项目1. 微服务父工程2. 创建子模块-shop-common3. 创建子模块--shop-product4. 创建子模块--shop-order服务治理组件1. 如何使用nacos2. 微服务客户端连接到nacos注册中心3. 消费端如何通过nacos调用提供者负载均衡1. 自己控制负载均衡2. ribbon完…

Nginx配置SSL证书

目录 获得证书 步骤一&#xff1a;购买证书 步骤二&#xff1a;申请证书 ​编辑 下载证书及证书转换&#xff1a;​编辑 配置SSL证书 在Nginx目录下新建certificate目录&#xff0c;并将下载好的证书/私钥等文件上传至该目录&#xff08;与配置文件同级目录&#xff09;。…

LiDAR点云转换到大地坐标系——简单粗标定

目录一、LiDAR和IMU位姿标定1.1安装角度标定1.2安装位置标定1.3部分代码二、点云转换到当地水平坐标系2.1基本理论2.1.1坐标系2.1.1.1激光雷达2.1.1.2导航坐标系/当地水平坐标系/大地坐标系2.1.2惯性传感器原理2.2点云转换试验2.2.1试验场地2.2.2试验内容2.2.3点云变换效果Wind…

C语言日记 37 类的友元(1)(全局函数做友元)

根据36 类和对象-友元-全局函数做友元_哔哩哔哩_bilibili复现出如下代码&#xff1a; 一、&#xff08;只是&#xff09;访问&#xff08;公开&#xff09;内部成员&#xff1a; #include <iostream> using namespace std; class Building { private:int ws;//卧室 pub…

NewStarCTF 公开赛-web

week1 HTTP cookie 修改admin 源码发现key GET和POST传参即可 Head?Header! User-Agent: CTF Referer: ctf.com X-Forwarded-For: 127.0.0.1我真的会谢 信息泄露 robots.txt www.zip 源码 /.index.php.swp NotPHP 函数绕过 if(file_get_contents($_GET[data]) &qu…

数据备份一般有哪些方式,如何进行数据备份

在如今这个时代&#xff0c;数据于我们而言是特别重要的&#xff0c;一旦发生丢失&#xff0c;可能会带来严重后果&#xff0c;如果你也很苦恼如何保证这些重要数据的安全&#xff0c;小编建议一定要对重要数据进行备份&#xff0c;以防发生丢失的意外。数据备份一般有哪些方式…

设计一个缓存策略,动态缓存热点数据

&#x1f468;‍&#x1f4bb;个人主页&#xff1a; 才疏学浅的木子 &#x1f647;‍♂️ 本人也在学习阶段如若发现问题&#xff0c;请告知非常感谢 &#x1f647;‍♂️ &#x1f4d2; 本文来自专栏&#xff1a; 常见场景解决方案 &#x1f308; 每日一语&#xff1a;努力不一…

C语言 0 —— 信息在计算机中的表示

计算机的电路 由 逻辑门电路组成。一个逻辑门电路可以看成为一个开关&#xff0c;每个开关的状态是“开” 则 高电位 对应 1 或者 “关” 则 低电位 对应 0 &#xff0c; 那么1和0 刚刚好用二进制数来表示&#xff1a; 每个位只能取1和0 &#xff0c;称为 one 个 bit &#…

谈一谈关于Linux内核编译详解原理

前言&#xff1a;为什么要做这个启动盘&#xff0c;因为内核编译是很危险的&#xff0c;中间出了错系统则直接崩溃&#xff0c;然后就无法开机了&#xff0c;你将看到一个_在你的左上角闪烁。知道启动盘可以帮你从外置设备启动系统&#xff0c;能启动系统才能恢复系统。1.编译前…

Java 线程和反射---尚硅谷Java视频学习

1.Java程序在运行得时候默认就会产生一个进程2.这个进程会有一个主线程3.代码都在主线程中执行 线程的生命周期 线程的执行方式 public class Java02_Thread {public static void main(String[] args) throws Exception {// TODO 线程 - 执行方式&#xff08;串行&#xff0c…