是js高级啊~
创始人
2024-02-07 04:33:59

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(); // aaanew bindFn(); // {}// bind绑定的this 高于 apply、call绑定的thislet bindFn = foo.bind("aaa");bindFn(); // aaabindFn.call("bbb"); // aaabindFn.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实现继承

寄生组合式继承(思想)

      // 函数作用: 创建一个空对象,对象的隐式原型指向ofunction 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 = 123function 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) //trueconst 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] = itemconsole.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.88constructor() {}// 类的静态属性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); // aaareturn "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)); // undefinedconst 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 start3. 遇到第一个setTimeout,将其放入宏任务队列4. 遇到第一个new Promise 打印promise1,resolve()时 将then回调放入微任务队列5. 遇到第二个setTimeout, 将其放入宏任务队列5. 打印26. 遇到queueMicrotask,将回调放入微任务队列7. 遇到第二个new Promise,resolve()时 将then回调放入微任务队列8. 打印script end9. 当前调用栈的代码执行完毕,去任务队列中找,根据浏览器在执行宏任务队列之前,会先清空微任务队列的机制,(队列是先进先出的数据结构)10. 依次打印 then4  queueMicrotask1  then311. 执行宏任务的第一个回调 打印setTimeout112. 遇到一个new Promise resolve()时 将then的回调放入微任务队列13. 打印then214. 当前宏任务执行完毕,执行下一个宏任务,监测微任务队列是否有任务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 start3. 执行setTimeout,将其给执行异步操作的线程,当时间结束后,异步操作的线程将其放入宏任务队列4. 执行async1()5. 打印async1 start6. 执行 await async2()7. 打印 async28. async2函数返回值相当于 Promise.resolve(undefined)9. async1中的await等到了fulfilled状态,将其后续代码加入到微任务中10. 执行new Promise 打印promise111. 执行resolve() 将then代码推入微任务队列12. 打印script end13. 执行微任务队列代码 依次打印async end  promise214. 执行宏任务队列任务, 打印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); //SyntaxErrorconsole.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 也可用matchAllconst bookRe = /《(.+?)》/;console.log(message.match(bookRe)); // 索引0 "《肖申克的救赎》"// 索引1 "肖申克的救赎"

命名组

  • 给捕获组添加名称(用数字记录组很困难),这样使用类似match和matchAll中就可以在group中获取了
  • 在括号开始的位置放置?
const reg = /(?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一定是<=0let 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是symbolconst 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) returneventFns.forEach((fn) => fn(...args))}// 取消off(eventName, eventFn) {let eventFns = this.eventMap[eventName]if (!eventFns) returnthis.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)
})

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...