Web前端最新JavaScript进阶知识汇总~,字节跳动面试流程

最后

技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

// 1.Object.create(proto, propertiesObject) 创建一个新对象,新对象的原型链将指向指定的原型对象的方法。
// 参数一:需要指定的新对象的原型对象,即新对象通过原型链继承了原型对象的属性和方法
// 参数二:可选参数,给新对象自身添加新属性以及描述器
// 2.Object.setPrototypeOf(obj, prototype) 用于设置一个对象的原型的方法
// 参数一:要设置原型的对象
// 参数二:该对象的新原型 
// 标准的寄生组合继承
// 使用 Object.create()
function Parent (name) {
  this.name = name
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name) {
  this.sex = 'boy'
  Parent.call(this, name)
}
// 与组合继承的区别
Child.prototype = Object.create(Parent.prototype)   // 创建了一个空对象,并且这个对象的__proto__属性是指向Parent.prototype
var child1 = new Child('child1')
child1                  // Child {sex: 'boy', name: 'child1'} 
child1.getName()        // child1 
child1.constructor      // Parent (name) this.name = name  指向继承的Parent
console.log(child1.__proto__)    
// 原型链:Parent {}[[Prototype]]:Object getName : ƒ ()constructor : ƒ Parent(name) [[Prototype]]:Object

// ===============================================================================================================
// 使用 Object.setPrototypeOf()
function Parent (name) {
  this.name = name
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name) {
  this.sex = 'boy'
  Parent.call(this, name)
}
// 与组合继承的区别
Object.setPrototypeOf(Child.prototype, Parent.prototype) // 将Child的原型设置为Parent的对象
var child2 = new Child('child2')
child2                 // Child {sex: 'boy', name: 'child1'}
child2.getName()       // child2
child2.constructor     // Child (name) {this.sex = 'boy'Parent.call(this, name)}
child2.__proto__      
// 原型链:Parent {constructor: ƒ}constructor : ƒ Child(name) [[Prototype]]:Object getName : ƒ ()constructor : ƒ Parent(name) [[Prototype]]:Object
// 可以得出Object.setPrototypeOf()方法设置对象的原型更加完美一些,它修复了子类原型中constructor的指向问题

// ===============================================================================================================
// function Parent(name){
  this.name = name
  this.face = 'cry'
  this.colors = ['white', 'black']
}
Parent.prototype.features = ['cute']
Parent.prototype.getFeatures = function (){
  console.log(this.features)
}
function Child(name){
  Parent.call(this, name)
  this.sex = 'boy'
  this.face = 'smile'
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
var child1 = new Child('child1')
child1.colors.push('yellow')
var child2 = new Child('child2')
child2.features = ['sunshine']
console.log(child1)
// 原型链:Child {name: 'child1', face: 'smile', colors: Array(3), sex: 'boy'}colors : (3) ['white', 'black', 'yellow']face : "smile"name : "child1"sex : "boy" [[Prototype]]:Parent constructor : ƒ Child(name) [[Prototype]]:Object features : ['cute']getFeatures : ƒ ()constructor : ƒ Parent(name) [[Prototype]]:Object
console.log(child2)
// 原型链:Child {name: 'child2', face: 'smile', colors: Array(2), sex: 'boy', features: Array(1)}colors : (2) ['white', 'black']face : "smile"features : ['sunshine']name : "child2"sex : "boy" [[Prototype]]:Parent constructor : ƒ Child(name) [[Prototype]]:Object features : ['cute']getFeatures : ƒ ()constructor : ƒ Parent(name) [[Prototype]]:Object
// 寄生组合继承算是ES6之前一种比较完美的继承方式。
// 它避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点
// 它拥有了原型链继承、构造继承和组合继承的所有继承方式的优点
// 只调用了一次父类构造函数,只创建了一份父类属性
// 子类可以用到父类原型链上的属性和方法
// 能够正常的使用instanceOf和isPrototypeOf方法
// Object.setPrototypeOf()方法会在运行时动态地改变对象的原型,这可能会对性能产生一些影响;
// 在创建对象时就应该使用Object.create()来设置原型链,而不是后期动态地改变原型链。

(5) 原型式继承
// 原理是创建一个构造函数,构造函数的原型指向对象,然后调用new 操作符创建实例,并返回这个实例,本质是一个浅拷贝
var cat = {
  heart: '❤️',
  colors: ['white', 'black']
}
var guaiguai = Object.create(cat)
var huaihuai = Object.create(cat)
console.log(guaiguai)    
// 原型链:{}[[Prototype]]:Object colors : (2) ['white', 'black']heart : "❤️" [[Prototype]]:Object
console.log(huaihuai)
// 原型链:{}[[Prototype]]:Object colors : (2) ['white', 'black']heart : "❤️" [[Prototype]]:Object
cat.colors.push('blue')     // 在原对象上个修改colors属性
guaiguai.colors             // ['white', 'black', 'blue'] 浅拷贝
// 同样的,对于 setPrototypeOf而言也是
var cat = {
  heart: '❤️',
  colors: ['white', 'black']
}
res1 = {}        // 因为Object.setPrototypeOf()没有返回值,所以先定义好一个对象
Object.setPrototypeOf(res1, cat);       // res1的原型指向cat对象

(6) 寄生式继承
// 在原型式继承的基础之上进行了一下优化
var cat = {
  heart: '❤️',
  colors: ['white', 'black']
}
function createAnother (original) {         // 在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。
    var clone = Object.create(original);
    clone.actingCute = function () {
      console.log('我是一只会卖萌的猫咪')
    }
    return clone;
}
var guaiguai = createAnother(cat)
var huaihuai = Object.create(cat)
huaihuai.heart             // '❤️'
guaiguai.actingCute()      // 我是一只会卖萌的猫咪

(7) 混入式继承(多继承)
// 我们一直都是以一个子类继承一个父类,而混入方式继承就是教我们如何一个子类继承多个父类的。
// 需要用到ES6中的方法Object.assign()
// 它的作用就是可以把多个对象的属性和方法拷贝到目标对象中,若是存在同名属性的话,后面的属性会把前面的覆盖掉
// Object.assign(target, ...sources) 静态方法将一个或者多个源对象中所有可枚举的自有属性复制到目标对象,并返回修改后的目标对象
// 参数一:需要应用源对象属性的目标对象,修改后将作为返回值
// 参数二:一个或多个包含要应用的属性的源对象
function Parent (sex) {
  this.sex = sex
}
Parent.prototype.getSex = function () {
  console.log(this.sex)
}
function OtherParent (colors) {
  this.colors = colors
}
OtherParent.prototype.getColors = function () {
  console.log(this.colors)
}
function Child (sex, colors) {
  Parent.call(this, sex)               
  OtherParent.call(this, colors)       // 新增的父类
  this.name = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype) // 新增的父类原型对象
Child.prototype.constructor = Child
var child1 = new Child('boy', ['white'])
console.log(child1) 
// 原型链:Child {sex: 'boy', colors: Array(1), name: 'child'}colors: ['white']name: "child"sex: "boy"[[Prototype]]: Parentconstructor: ƒ Child(sex, colors)getColors: ƒ ()arguments: nullcaller: nulllength: 0name: ""prototype: {constructor: ƒ}[[FunctionLocation]]: VM23262:10[[Prototype]]: ƒ ()[[Scopes]]: Scopes[1][[Prototype]]: ObjectgetSex: ƒ ()constructor: ƒ Parent(sex)[[Prototype]]: Object
child1.__proto__               // Parent {getColors: ƒ, constructor: ƒ}
child1.__proto__.__proto__     // {getSex: ƒ, constructor: ƒ}
// 可以看出Parent.prototype.getSex()和OtherParent.prototype.getColors(),他们不在同一个原型上, 正常来讲是在同一个原型上

// ===============================================================================================================
function Parent (sex) {
  this.sex = sex
}
Parent.prototype.getSex = function () {
  console.log(this.sex)
}
function OtherParent (colors) {
  this.colors = colors
}
OtherParent.prototype.getColors = function () {
  console.log(this.colors)
}
function Child (sex, colors) {
  Parent.call(this, sex)
  OtherParent.call(this, colors) // 新增的父类
  this.name = 'child'
}
Object.assign(Parent.prototype, OtherParent.prototype) // 新增的父类原型对象,其它原型上的属性添加的目标原型上
Child.prototype = Object.create(Parent.prototype)      // 创建一个Parent的新对象,新对象指向Child的原型
// Object.setPrototypeOf(Child.prototype, Parent.prototype)
Child.prototype.constructor = Child          // 认亲爹
var child1 = new Child('boy', ['white'])
console.log(child1)                     
// 原型链:Child {sex: 'boy', colors: Array(1), name: 'child'}colors : ['white']name : "child"sex : "boy" [[Prototype]]:Parent constructor : ƒ Child(sex, colors) [[Prototype]]:Object getColors : ƒ ()getSex : ƒ ()constructor : ƒ Parent(sex) [[Prototype]]:Object
child1.__proto__.__proto__           
// {getSex: ƒ, getColors: ƒ, constructor: ƒ}  getSex和getColors现在已经在同一个原型上了
console.log(Child.prototype.__proto__ === Parent.prototype)           // true
console.log(Child.prototype.__proto__ === OtherParent.prototype)      // false
console.log(child1 instanceof Parent)                                 // true
console.log(child1 instanceof OtherParent)                            // false
// 混入式继承的缺点:对于两个或者多个父类,只能维持一个instanceof正常。

(8) Class 继承
  • 主要是依靠两个关键字:extendsuper
class Parent {
  constructor (name) {
    this.name = name
  }
  getName () {
    console.log(this.name)
  }
}
class Child extends Parent {        // class通过extends关键字实现继承,让子类继承父类的属性和方法
  constructor (name) {
    super(name)                     // super在这里表示父类的构造函数,用来新建一个父类的实例对象
    this.sex = 'boy'
  }
}
var child1 = new Child('child1')
console.log(child1)    
// 原型链:Child {name: 'child1', sex: 'boy'}name : "child1"sex : "boy" [[Prototype]]:Parent constructor : class Child[[Prototype]]:Object constructor : class Parent getName:ƒ getName() [[Prototype]]:Object
// 从完整的原型链可以看出class的继承方式完全满足于寄生组合继承

// ===============================================================================================================
class Parent {
  constructor () {
    this.name = 'parent'
  }
}
class Child extends Parent {
  constructor () {
    // super(name) // super注释掉   
  }
}
var child1 = new Child()    // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor 
/* 
【重点】
	1.ES6规定,子类必须在constructor()方法中调用super(),否则就会报错。
	  这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,
   	  然后再对其进行加工,添加子类自己的实例属性和方法。如果不先调用super()方法,子类就得不到自己的this对象。
    2.如果使用了extends实现继承的子类内部没有constructor方法,则会被默认添加constructor和super;
      若子类中有constructor方法,必须得在constructor中调用一下super函数,否则会报错。
    4.需要注意的地方是,在子类的constructor方法中,只有调用super()之后,才可以使用this关键字,否则会报错。
      这是因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。
      可以理解为,新建子类实例时,父类的构造函数必定会先运行一次
*/

// ===============================================================================================================
// super关键字,既可以当作做函数使用,也可以当做对象使用。
// 1.super当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时super内部的this指向子类
class Parent {
  constructor () {
    console.log(new.target.name)     // 在构造函数内部使用new关键字创建对象时,返回该构造函数的名称。
  }
}
class Child extends Parent {
  constructor () {
    var instance = super()          // 在子类的constructor中super()就相当于是Parent.prototype.constructor.call(this)
    /* 调用super()的作用是形成子类的this对象,把父类的实例属性和方法放到这个this对象上面;
       子类在调用super()之前,是没有this对象的,任何对this的操作都要放在super()的后面。*/
    console.log(instance)
    console.log(instance === this)
  }
}
var child = new Child()   
// Child         
// Child {}  super当做函数来使用时,虽然它代表着父类的构造函数,但是返回的却是子类的实例,也就是说super内部的this指向的是Child。  
// true
/*
super当做函数使用时的限制:	 
	1.子类constructor中如果要使用this的话就必须放到super()之后
	2.super当成函数调用时只能在子类的construtor中使用
*/

// super()在子类构造方法中执行时,子类的属性和方法还没有绑定到this,如果存在同名属性,此时拿到的是父类的属性。
class A {
  name = 'A';
  constructor() {
    console.log(this.name);
  }
}
class B extends A {
  name = 'B';
}
new B();  // A  原因在于super()执行时,B的name属性还没有绑定到this,所以this.name拿到的是A的name属性

// super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
// 在普通方法中:
class A {
  p() {
    console.log('tzw');
  }
}
class B extends A {
  constructor() {
    super();
    super.p();         // 2 就是将super当作一个对象使用, super.p()就相当于A.prototype.p()
    // 需要注意的是由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
    // 或者说定义在父类原型对象上的属性和方法,super就可以取到
  }
}
new B();
// ES6规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
class A {
  constructor() {
    this.x = 1;
    this.y = 1;
  }
  print() {
    console.log(this.x);
  }
}
class B extends A {
  constructor() {
    super();         
    this.x = 2;
    console.log(this.y)      // 1
    super.y = 2;    // 由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
    console.log(super.y)     // undefined
    // super.x赋值2,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined
	console.log(this.y)      // 2
  }
  m() {
    super.print();           // 2 调用的是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)
  }
}
let b = new B();
b.m()     // 2

// ===============================================================================================================
// 2.super作为对象时用在静态方法之中,这时super将指向父类;在普通方法之中指向父类的原型对象。
class Parent {
  constructor (name) {
    this.name = name
  }
  getName () {
    console.log(this.name)
  }
}
Parent.prototype.getSex = function () {
	console.log('boy')              // child1
}
Parent.getColors = function () {
  console.log(['white'])
}
class Child extends Parent {
  constructor (name) {
    super(name)
    super.getName()
    // console.log(super);         // 报错 要能清晰地表明super的数据类型(作为函数还是对象使用)才不会报错
  }
  instanceFn () {
    super.getSex()                 // super在普通方法之中指向父类的原型对象
  }
  static staticFn () {
    super.getColors()   // 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。  
  }
}
var child1 = new Child('child1') 
child1.instanceFn()  // boy 在普通方法child1.instanceFn里面,super.getSex指向父类的原型对象
Child.staticFn()     // ['white'] 静态方法Child.staticFn里面,super.staticFn指向父类的静态方法。这个方法里面的this指向的是Child,而不是Child的实例。
//由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字
// extends后面接着的继承目标不一定要是个class,只要父类是一个有prototype属性的函数就能被子类继承

3) 多态性
  • 多态性是指具体多种形态或者实现方式,Java中的多态性允许类的子类定义它们自己的唯一行为,并且还共享父类的一些相同功能。
// 多态最根本的作用就是通过把过程化的条件语句转化为对象的多态性,从而消除这些条件分支语句。
// 实际上是一种编程的思想
// 对于JS是具有与生俱来的多态性它的变量类型在运行期是可变的,程序并没有要求我指定它的类型,也就是它并不存在这种类型之间的耦合关系。

4) 代理与反射

在JavaScript中通过代理和反射,开发人员可以更加灵活地操作和控制对象的行为,从而实现更高级的功能和自定义行为。

  • 先了解什么是属性描述符?

JavaScript中的属性描述符("Property Descriptor"本质上是一个JavaScript 普通对象)是用于描述一个属性的相关信息。每个属性都有一个相关联的属性描述符,它由以下几个属性组成:

​ 1.value:属性的值。可以是任何有效的JavaScript值。默认为undefined

​ 2.writable:该属性是否可以被重新赋值存取器属性。如果设置为true,则属性的值可以被修改。默认为true

​ 注意:属性描述符中如果配置了 get 和 set 中的任何一个,则该属性不再是一个普通属性,而变成了存取器属性。

​ get()读值函数:如果一个属性是存取器属性,则读取该属性时,会运行 get 方法,并将 get 方法得到的返回值作为属性值;

​ set(newVal)存值函数:如果给该属性赋值,则会运行 set 方法,newVal 参数为赋值的值。

​ * value 和 writable 属性不能与 get 和 set 属性共存,二者只能选其一

​ 存取器属性最大的意义,在于可以控制属性的读取和赋值,在函数里可以进行各种操作。

​ 3.enumerable:表示属性是否可枚举。如果设置为true,则属性可以在for..in循环中被枚举。默认为true

​ 4.configurable:表示属性是否可配置。如果设置为true,则允许修改属性的描述符和删除属性。默认为true

/*
1.获取对象属性描述符
   	Object.getOwnPropertyDescriptor(对象, 属性名)   获取一个对象的某个属性的属性描述符
   	Object.getOwnPropertyDescriptors(对象)         获取某个对象的所有属性描述符
2.为某个对象添加属性时 或 修改属性时,配置其属性描述符,使用以下这两种方法
	Object.defineProperty(对象, 属性名, 描述符)      设置一个对象的某个属性
	Object.defineProperties(对象, 多个属性的描述符);  设置一个对象的多个属性
需要注意的是,如果尝试修改不可配置的属性描述符,或者通过`Object.defineProperty()`方法定义已经存在的属性,严格模式下会抛出错误,非严格模式下会被忽略。
*/
// 主要介绍下设置多个属性的描述符
var obj = {
    property1: false,
    property2: false,
};
Object.defineProperties(obj, {
  'property1': {
    value: true,
  },
  'property2': {
    value: 'Hello',
    enumerable: false,      // 设置为不可枚举
  }
});
for (let i in obj) { console.log(i) }     // property1 property2不会被遍历,因为for...in遍历的是对象所有可遍历(enumerable)的属性 


// ===============================================================================================================
// 描述符修改的几种情况
// 1.
let person1 = {};
Object.defineProperty(person1, "name", {
    configurable: true,                                       // 允许修改属性的描述符和删除属性
    writable: true,                                           // 属性的值可以被修改
    value: "abc",
})
Object.defineProperty(person1, "name", {writable: false})     // 第二次将writeable修改为第一次设置的相反值
console.log(person1.name);      // abc
// 2.
let person2 = {};
Object.defineProperty(person2, "name", {
    configurable: true,                                       // 允许修改属性的描述符和删除属性
    writable: false,                                          // 属性的值不可以被修改
    value: "def",
})
Object.defineProperty(person2, "name", {writable: true})     // 第二次将writeable修改为第一次设置的相反值
console.log(person2.name);      // abc
// 3.
let person3 = {};
Object.defineProperty(person3, "name", {
    configurable: false,                                    // 不允许修改属性的描述符和删除属性
    writable: true,                                         // 属性的值可以被修改
    value: "123",
})
Object.defineProperty(person3, "name", {writable: false})   // 第二次将writeable修改为第一次设置的相反值
console.log(person3.name);     // 123     
// 4.
let person4 = {};
Object.defineProperty(person4, "name", {
    configurable: false,
    writable: false,
    value: "456",
})
Object.defineProperty(person4, "name", {writable: true})   // 报错 TypeError: Cannot redefine property:
/*
	1.当configurable为true 时,一切都是可以修改的
	2.当configurable为false时,第二次修改为原值时不报错,如果第二次修改为相反值时存在一种特殊情况
	  configurable 与 writable 有一个值为true时value的值可修改 writable--->false
    3.当configurable 为 false 时, 除了第2种情况之外,其它的修改都会报错。
    ----------------------------------------------------------------------------------------
    enumerable 看 configurable 
      (configurable和enumerable:这两个选项之间没有直接的互斥关系,但它们与其他属性描述符选项存在相互关系)
	value 看 configurable和writable
	writable 看 configurable和writable
	value和writable 与 get与set 互斥
	get和set:如果同时设置了get和set选项,则不能再设置value或writable选项;
	  get和set方法提供了属性的自定义读取和写入行为,因此value和writable选项变得无意义。
*/

ES6 新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。具体地说,**可以给目标对象(target)定义一个关联的代理对象,而这个代理对象可当作一个抽象的目标对象来使用。**因此在对目标对象的各种操作影响到目标对象之前,我们可以在代理对象中对这些操作加以控制,并且最终也可能会改变操作返回的结果。

(1) 代理
  • 代理(Proxy)是一种机制,允许你创建一个代理对象来包装目标对象,并拦截该对象的操作。通过使用代理,你可以在目标对象上定义自定义的行为,例如拦截属性读取、属性设置、函数调用等操作。这使得你可以对对象的访问和修改进行更精细的控制。
  • 代理(Proxy)能使我们开发者拥有一种间接修改底层方法的能力,从而控制用户的操作【在逆向中多用来进行hook】。
  • 语法: let target = { /目标对象的属性/ }; //目标对象 let handler = { /用来定制拦截操作/ }; //拦截层对象 let proxy = new Proxy(target, handler); //实例化
const target = {                  // 目标对象
  name: 'John'
};
const handler = {                 // 处理器对象
  // 捕获器在处理程序对象中以方法名为键
  get(target, property, receiver) {
    console.log(`Reading property '${property}'`);
    return target[property];      // 返回目标对象的属性值
  },
  set(target, property, value, receiver) {
    console.log(`Setting property '${property}' to '${value}'`);
    target[property] = value;     // 设置目标对象的属性值
    return true;                  // 设置成功返回true
  }
};
const proxy = new Proxy(target, handler);     // 创建代理对象,代理对象使用Proxy构造函数将目标对象和处理程序对象handler结合起来
proxy.name               // 查看代理对象的属性
// Reading property 'name'  
// john                  // get捕获器返回值
proxy.age = 26           // 修改代理对象设置属性
// Setting property 'age' to '26'  
// 26                    // set捕获器返回值

/*
常见的代理捕获方法:
	1.get(target, property, receiver):捕获属性的读取操作。
	2.set(target, property, value, receiver):捕获属性的赋值操作。
	3.has(target, property):捕获in运算符的操作。
	4.deleteProperty(target, property):捕获delete运算符的操作。
	5.apply(target, thisArg, argumentsList):捕获函数调用的操作
*/

(2) 反射
  • 反射(Reflection)是指通过内置的Reflect对象,提供一组方法来操作和查询对象的行为。Reflect 它提供了一系列方法,可以让开发者通过调用这些方法,访问一些 JS 底层功能;由于它类似于其他语言的反射,因此取名为 Reflect
  • 使用 Reflect 可以实现诸如:属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能。
const obj = {
  name: 'tzw',
  greeting() {
    return `Hello, ${this.name}!`;
  }
};
console.log(Reflect.get(obj, 'name'));    // tzw 获取obj对象的name属性
Reflect.set(obj, 'age', 26);              // true 设置obj对象的age属性
console.log(Reflect.has(obj, 'age'));     // true 查看obj对象是否有age属性
Reflect.deleteProperty(obj, 'age');       // true 删除obj对象的age属性
console.log(obj.age)                      // undefined
const result = Reflect.apply(obj.greeting, { name: 'qdd' }, []);    // 改变obj对象的greeting方法的this指向
console.log(result)                       // qdd 
/*
常见的代理捕获方法
	1.Reflect.get(target, property, receiver):获取对象的属性值。
	2.Reflect.set(target, property, value, receiver):设置对象的属性值。
	3.Reflect.has(target, property):检查对象是否具有指定属性。
	4.Reflect.deleteProperty(target, property):删除对象的属性。
	5.Reflect.apply(func, thisArg, args):调用函数并传递参数。
*/

// 典例
// eg1:(hook)
test6 = {a: 10}
var test6 = new Proxy(test6, {
        defineProperty: function(target, property, descriptor) {
        debugger;
        console.log(arguments)
        return Reflect.defineProperty(...argume   nts)
    }
});
attributes = {
    configurable: false,
    enumerable: false,
    writable: false,
    value: 1000,
}
Object.defineProperty(test6, 'a', attributes)

// eg2:
var proxy = {};
var target = proxy
var handler = {
    getPrototypeOf: function (){console.log(arguments);return Reflect.getPrototypeOf(this)}
}
proxy1 = new Proxy(target, handler)

4. 异步

1) 单线程模型

单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。 注意,JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程),其他线程都是在后台配合。

2) 同步任务和异步任务

程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。 同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。 异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。

3) 任务队列和事件循环

JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。 首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。 JavaScript 引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。

4) Event Loop 事件循环机制

浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

event loop 执行顺序: 1.一开始整个脚本作为一个宏任务执行(可以是一个js脚本,或者script标签) 2.执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列 3.当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完 4.执行浏览器UI线程的渲染工作 5.检查是否有Web Worker任务,有则执行 6.执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

什么是宏任务队列、微任务队列? 宏任务队列,也叫宏队列:script 、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering 微任务队列,也叫微队列:MutationObserver、Promise.then()或catch()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、 async 、Node独有的process.nextTick等。 需要重点关注的是:setTimeout、setInterval 、Promise.then、 async 在所有任务开始的时候,由于宏任务中包括了script,所以浏览器会先执行一个宏任务,在这个过程中你看到的延迟任务(例如setTimeout)将被放到下一轮宏任务中来执行。

5) Promise对象
  • Promise(是一个对象,也是一个构造函数)是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。
  • Promise 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
  • Promise 的设计思想是,所有异步任务都返回一个 Promise 实例。
  • Promise 实例有一个then方法,用来指定下一步的回调函数。
(1) Promis的用法
// 创建一个异步对象
var promise = new Promise((resolve, reject) => {    // 回调函数写成更简介的箭头函数,是一个标准的promise
  // ...
  if (/* 异步操作成功 */){
    resolve(value);
  } else { /* 异步操作失败 */
    reject(new Error());
  }
});
/*
  1.Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己实现。
  2.resolve函数的作用是,将Promise实例的状态从“未完成”变为“成功”(即从pending变为fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
  3. reject函数的作用是,将Promise实例的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
*/

Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态:

  • 异步操作未完成(pending)
  • 异步操作成功(fulfilled)
  • 异步操作失败(rejected)

这三种状态里边,fulfilled 和rejected 合在一起称为resolved(已定型)。 这三种的状态的变化途径只有两种:

  • 从“未完成”到“成功 ”
  • 从“未完成”到“失败”

一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。

所以,Promise 的最终结果只有两种:

  • 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled
  • 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected
// 初始态
var callback = function(resolve, reject){}
const promise1 = new Promise(callback)
console.log(promise1)
// Promise {<pending>}

// 成功态
var callback = function(resolve, reject){resolve()}
const promise1 = new Promise(callback)
console.log(promise1)
// Promise {<fulfilled>: undefined}

// 失败态
var callback = function(resolve, reject){reject()}
const promise1 = new Promise(callback)
console.log(promise1)
// Promise {<rejected>: undefined}
// 以上就是promise1的三种状态

// 打印顺序是什么?
const promise1 = new Promise((resolve, reject) => {    // Promise 不是一个异步操作
  console.log('promise1')
})
console.log('1', promise1);
// promise1
// 1 Promise {<pending>}
/*
	创建promis对象的时候,此时,它是一个同步操作,promise1.then()才是一个异步操作;
	所以,执行顺序从上至下,先遇到new Promise,执行该构造函数中的代码,打印promise1;
	然后执行同步代码打印1,此时promise1没有被resolve或者reject,因此状态还是pending。
*/
/*
promise1 原型:Promise.prototype
	constructor: ƒ Promise()   这玩意没啥说的
	then: ƒ then()         	   主要看这个
	catch: ƒ catch()           看名字就应该知道它应该是错误回收
	finally: ƒ finally()       看名字就应该知道它是最后执行的
*/

(2) Promise.then
  • Promise 实例的then方法,用来添加回调函数。
// 1. promise是成功态时
const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
  console.log(2);
});
promise.then(() => {    
  console.log(3);
});
console.log(4);
console.log(promise);
// 1
// 2
// 4
// Promise {<fulfilled>: 'success'}
// 3
/* 
执行步骤:
	1.从上至下,先遇到new Promise,执行其中的同步代码打印1
	2.再遇到resolve('success'), 将promise的状态改为了fulfilled(成功态)并且将值保存下来
	3.继续执行同步代码打印2
	4.跳出promise对象,往下执行,碰到promise.then这个微任务,将其加入微任务队列【注意,这里实际上发生的过程】
	5.执行同步代码打印4
	6.执行同步代码promise,打印promise的状态
	7.本轮宏任务队列全部执行完毕,检查微任务队列,发现promise.then这个微任务且状态为fulfilled,执行它。
*注意:
	在看到promise.then()还会创建一个promis对象,它的状态为pending(初始态);
	当then发方法里边的函数执行完且没有报错的情况下,它的状态变为fulfilled(成功态);
	如果报错,就会变成rejected(失败态)。
*/

// 2. promise是失败态时
const promise = new Promise((resolve, reject) => {
  console.log(1);
  reject('failed')
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
// 1
// 2
// 4
/* 
执行步骤:
	1.从上至下,先遇到new Promise,执行其中的同步代码打印1
	2.再遇到reject('failed'), 将promise的状态改为了rejected(失败态)并且将值保存下来
	3.继续执行同步代码打印2
	4.跳出promise对象,往下执行,碰到promise.then,只有promise是成功态的时候,promise.then里面的内容才会被放到微任务队列里边,
	5.执行同步代码打印4
	6.执行同步代码promise,打印promise的状态
	7.本轮宏任务队列全部执行完毕,检查微任务队列,没有任务。
*注意:
	promise.then(), 只有promise是fulfilled(成功态)的时候,promise.then里边的内容才会被放到微任务队列
*/
const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {     // 此时的promise的状态是pending,不会放到微任务队列
  console.log(3);
});
console.log(4);
// 1
// 2
// 4

  • then方法可以接受两个回调函数,第一个是异步操作成功时(变为fulfilled状态)的回调函数,第二个是异步操作失败(变为rejected)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。
const then_callback = function(){console.log(arguments)}
const promise = new Promise((resolve, reject) => {
  resolve('success')         // 这个成功态的值会被promise.then的回调函数所接收到,它的第一个参数就是这个值
});
const result = promise.then(then_callback);      
console.log(result);              
// Promise {<pending>}  当一个已完成的“F”状态的Promise遇到then方法,一定瞬间产生一个新的Promise,状态为P 
// { "0": "success" } 

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {     //promise1调用then的时候,不管后面跟的什么,一瞬间创建一个新的Promise对象
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
// promise1
// 1 Promise {<fulfilled>: 'resolve1'}
// 2 Promise {<pending>}
// resolve1
/*
执行步骤:
    1.从上至下,先遇到new Promise,执行该构造函数中的代码打印promise1
    2.碰到resolve函数, 将promise1的状态改变为fullfiled,并将结果保存下来
    3.碰到promise1.then这个微任务,将它放入微任务队列【只要遇到then方法,一定产生一个新的Promise】
    4.promise2是一个新的状态为pending的Promise
    5.执行同步代码打印1, 同时打印出promise1的状态是fulfilled
    6.执行同步代码打印2,同时打印出promise2的状态是pending
    8.宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务且状态为fulfilled,执行它。
*/
const fn = () =>
  new Promise((resolve, reject) => {
    console.log(1);
    resolve("success");
  });
console.log("start");
fn().then(res => {
  console.log(res);
});
// start
// 1
// success

  • 对于 Promise这个构造函数,如果不去new 它的话
const promise1 = Promise.resolve('success')  // 产生了一个已完成的 Promise
const promise2 = promise1.then((res) => {console.log(res)})
console.log(promise2)
// Promise {<pending>}
// success
// 在控制台再次打印
console.log(promise2)
// Promise {<fulfilled>: undefined}
/*
可以得出结论:promise1.then的返回值是promise实例化对象【已完成】,当.then的一瞬间,创造一个状态为Pending的promise,如果之前的promise已完成,就一瞬间把回调函数放入微队列;这个promise返回值的状态是 pending,但是一旦微队列里面的内容执行完毕了,那么,这个值就变成了fulfilled状态
*/


// ===============================================================================================================
// 采用链式的then,可以指定一组按照次序调用的回调函数;
// 前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),
// 这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
// 经典案列:
Promise.resolve()
    .then(() => {
        console.log(0);
        return Promise.resolve(4);      
    }).then(
    (res) => {
        console.log(res)
    }
)
Promise.resolve()
    .then(
    () => {
        console.log(1);
    }).then(
    () => {
        console.log(2);
    }).then(
    () => {
        console.log(3);
    }).then(
    () => {
        console.log(5);
    }).then(
    () => {
        console.log(6);
    })
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 解析参考:https://www.zhihu.com/question/453677175?rf=512445784

6) Promise结合setTimeout
  • 根据Event Loop 事件机制,setTimeout 会生成一个宏任务队列
console.log('start')
setTimeout(() => {
  console.log('time')
})
//.then方法接收两个回调函数,当Promise是成功态时把第一个回调函数放到微任务队列里边去,失败态时将第二个回调函数放到微任务队列里边去。
Promise.resolve().then(() => {  
  console.log('resolve')
})
console.log('end')
// start
// end
// resolve
// time
/*
执行步骤:
	1.刚开始整个脚本作为一个宏任务来执行,对于同步代码直接压入执行栈进行执行,因此先打印出start和end。
    2.setTimout作为一个宏任务被放入宏任务队列(下一个宏任务队列,并不是当前宏任务队列)
    3.Promise.then作为一个微任务,因为是成功态,所以被放入微任务队列
    4.本次宏任务执行完,检查微任务,发现Promise.then,执行它
    5.接下来进入下一个宏任务,发现setTimeout,执行。
*/

// 当我们new一个Promise的时候它是一个同步执行的任务,
// 只有调用.then方法的时候才会把then里边的回调函数放到微任务队列里边去
const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);
// 1
// 2
// 4
// timerStart
// timerEnd
// success
/*
执行步骤:
    1.从上至下,先遇到new Promise,执行该构造函数中的代码1
    2.然后碰到了定时器,将这个定时器中的函数放到下一个宏任务的延迟队列中等待执行
    3.执行同步代码2
    4.跳出promise函数,遇到promise.then,但其状态还是为pending,这里理解为先不执行
    5.执行同步代码4
    6.一轮循环过后,进入第二次宏任务,发现延迟队列中有setTimeout定时器,执行它
    7.首先执行timerStart,然后遇到了resolve,将promise的状态改为resolved且保存结果并将之前的promise.then推入微任务队列
    8.继续执行同步代码timerEnd
    9.宏任务全部执行完毕,查找微任务队列,发现promise.then这个微任务,执行它。
*/


// ===============================================================================================================
// *宏任务队列中产生的微任务会在当前宏任务完成后立即执行
setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(() => {
    console.log('promise')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')
// start
// timer1
// promise
// timer2
// 当宏任务执行过程中产生微任务的时候,会直接轮询微任务;
// 也可以理解为setTimeout会独立启用一个宏任务队列,而不是去排队。

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');
// start 
// promise1
// timer1
// promise2
// timer2
/*
执行过程:
    1.刚开始整个脚本作为第一次宏任务来执行,我们将它标记为宏1,从上至下执行
    2.遇到Promise.resolve().then这个微任务,将then中的内容加入第一次的微任务队列标记为微1
    3.遇到定时器timer1,将它加入下一次宏任务的延迟列表,标记为宏2,等待执行(先不管里面是什么内容)
    4.执行宏1中的同步代码打印start
    5.第一次宏任务(宏1)执行完毕,检查第一次的微任务队列(微1),发现有一个promise.then这个微任务需要执行
    6.执行打印出微1中同步代码promise1,然后发现定时器timer2,将它加入宏任务队列宏2的后面,标记为宏3
    7.第一次微任务队列(微1)执行完毕,执行第二次宏任务(宏2),首先执行同步代码打印timer1
    8.然后又遇到了Promise.resolve().then这个微任务假定它是promise2,将它加入此次循环的微任务队列,标记为微2
    9.宏2中没有同步代码可执行了,查找本次循环的微任务队列(微2),发现了promise2,执行它
    10.第二轮执行完毕,执行宏3,打印出timer2
    *注意:setTimeout 会独立启动一个宏任务队列,而不是去当前宏任务队列去排队。
*/

// Promise.resolve().then 产生的微任务会进入本轮微任务队列, 而不会进入下一轮 
Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(() => {
  console.log('promise3');
  const timer3 = setTimeout(() => {
    console.log('timer3')
    Promise.resolve().then(() => {console.log('promise4')})
  }, 0);
    const timer4 = setTimeout(() => {
    console.log('timer4')
  }, 0);
});
console.log('start');
// start
// promise1 
// promise3 
// timer1 
// promise2 
// timer2  
// timer3 
// promise4 
// timer4


// ===============================================================================================================
// setTimeout加上延时
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000) // 注意这个延时
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')     // thrw抛出报错,new Error不会报错,只是生成一个错位的对象
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 0)
// promise1 Promise {<pending>}
// promise2 Promise {<pending>}
// Uncaught (in promise) Error: error!!!
// promise1 {<fulfilled>: 'success'}
// promise2 Promise {<rejected>: Error: error!!!}
/*
	throw 语句用来抛出一个用户自定义的异常。
	当前函数的执行将被停止(throw之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个catch块。
	如果调用者函数中没有catch块,程序将会终止。
*/

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
    console.log("timer1");
  }, 1000);          // 注意这个时间
  console.log("promise1里的内容");
});
const promise2 = promise1.then(() => {
  throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {
  console.log("timer2");
  console.log("promise1", promise1);
  console.log("promise2", promise2);
}, 0);               // 注意这个时间
// promise1里的内容
// promise1 {<pending>}
// promise2 {<pending>}
// timer2
// promise1 {<pending>}    
// promise2 {<pending>}
// timer1           // timer2比timer1先打印,原因在于setTimeout执行的定时器时间
// Uncaught (in promise) Error: error!!!  输出的时候,报错的优先级要小于日志的优先级
/*
setTimeout解释:
	当第一个 setTimeout() 被调用时,它会将回调函数推入异步执行队列中,并设定一个定时器,经过指定的时间后将该回调函数移出队列并执行。
	如果在定时器到期之前有其他的代码被执行,则该定时器会被暂时挂起,待其他的代码执行完毕后再继续执行这个定时器。
	当当前的执行栈为空时,JavaScript 引擎会检查异步执行队列,并按照队列的先后顺序执行其中的回调函数;
	对于多个 setTimeout() 的情况,引擎会按照设定的时间间隔依次执行它们的回调函数。
	需要注意的是,具体的执行时间可能会受到一些因素的影响,如系统负载、浏览器的事件循环机制等。【当某个定时器的时间到期时,如果当前的执行栈仍然有其他任务在执行,JavaScript 引擎会等待执行栈为空后才会处理定时器回调函数。】因此,设置多个定时器时,它们会按照设定的时间间隔依次添加到异步执行队列中,但具体的执行时间会受到其他因素的影响。并不能保证它们会严格按照设定的时间间隔依次执行,而是在当前执行栈为空时,尽可能快速地按照先后顺序执行。
*/

7) Promise A+ 规范
// 1
const promise = new Promise((resolve, reject) => {
  resolve("success1");
  reject("error");           // 没有任何作用,因为Promise的状态一旦改变,就永久保持该状态,不会再变了
  resolve("success2");       // 没有任何作用,因为Promise的状态一旦改变,就永久保持该状态,不会再变了
});
promise.then(res => {
    console.log("then: ", res);
  }).catch(err => {          // promise抛出一个错误,就被catch()方法指定的回调函数捕获。
    console.log("catch: ", err);
})
// then:  success1
// 总结:promise只要状态从P转变成 F/R,状态就不会再次被改变

// 2
let P1 = Promise.reject().then(()=>{})
console.log(P1)
// Promise {<pending>}	Promise.then状态为pending 
// Uncaught (in promise) undefined  这个报错是Promise.reject()产生的,输出时日志的优先级高于报错的优先级
console.log(P1)         // 再次打印
// Promise {<rejected>: undefined} then方法的第二个参数是rejected状态的回调函数,由于没有第二个回调函数所以是undefined
// 总结:rejected状态的Promis.then方法没有第二个参数,就会返回rejected状态, 如果有第二个参数会返回fulfilled状态
// 3
let P2 = Promise.resolve().then(undefined,()=>{console.log(1)})
console.log(P2)
// Promise {<pending>}  // Promise.then状态为pending
console.log(P2)         // 再次打印
// 总结:fulfilled状态的Promis.then方法没有第一个参数,就会返回fulfilled状态
// 4
let P3 = Promise.reject().then(()=>{}, ()=>{})
console.log(P3)
// Promise {<pending>}
console.log(P3)         // 再次打印
// Promise {<fulfilled>: undefined}
/*
说一下为什么第二次打印状态变为fulfiled或者rejected:
	1. 是因为 Promise 的状态改变和回调函数的执行是异步的;
	2. 当调用 Promise 的方法(比如 resolve()、reject())时,Promise 对象的状态并不会立即改变,而是在 JavaScript 的事件循环机制中等待下一个微任务阶段执行。
	3. 如果在调用 Promise 方法后立即打印 Promise 对象的状态,那么很可能此时 Promise 对象的状态尚未改变,仍然是 "pending"。
    4. 只有在后续的微任务阶段中,JavaScript 引擎会检查微任务队列中是否有待处理的回调函数,并依次执行这些函数。这时才会执行 Promise 对象的回调函数,并且状态可能被改变为 "fulfilled" 或 "rejected"。
    小tip: 想要准确获取 Promise 对象的最新状态,可以在 then() 方法注册的回调函数中进行状态的打印,这样可以确保你在 Promise 状态变为 "fulfilled" 或 "rejected" 之后再打印对应的状态。
*/

// 5
const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch()
// 报错
// 总结:.then不能返回其本身,否则报错。 这两句话都报错,但是promise的返回值还是可以接收到的。
// 再次印证了观点:promise.then出现的一瞬间,就会出现一个新的promise而不会去管.then的回调函数的内容。

8) Prmise.catch
  • Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
  • then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");      // 没有任何作用,因为Promise的状态一旦改变,就永久保持该状态,不会再变了
});
promise.then(res => {
    console.log("then1: ", res);
  }).then(res => {
    console.log("then2: ", res);
  }).catch(err => {        // catch可以捕捉到任何时候上层还没有捕捉的错误
    console.log("catch: ", err);
  }).then(res => {
    console.log("then3: ", res);
  })
// catch:  error
// then3:  undefined
/*
执行步骤:
	1.由于new的Promise对象,暂定为P0的状态是一个rejected(拒绝态),拒因是 error;
	2.调用第一个then方法,由于P0的状态已经确定为rejected,then方法没有第二参数,所以当前Promise.then状态为rejected 记为P1;
	3.调用第二个then方法,then方法还是没有第二参数,所以当前Promise.then状态为rejected 记为P2;
	4.之后遇到catch方法,此时Promise.catch的状态是pending,由于P2是rejected,所以就捕获到了错误,并接收到拒因,控制台打印拒因,此时状态就会变为fulfilled 记为C1;
	5.调用最后一个then方法时,由于C1的状态为fulfilled, 所以执行then方法的第一个参数,由于C1没有传值,所以控制台打印是undefined
*/
// 根据以上的案列是否可以理解为,then方法实际上是包含错误捕捉功能的,then方法的第二个参数实际上就是 catch

Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });
// 1
// 2
// 即使catch没有捕捉到错误,它也会正常返回一个fulfilled状态的Promise对象,并且会将上一个promise对象的参数正常传递给下一个then方法
// 如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。
 Promise.reject(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {           // 捕获到失败态的Promise对象,并且拿到拒因,
    console.log(err);
    return 3                // 向下传递了一个参数
  })
  .then(res => {
    console.log(res);
  });
// 1
// 3

Promise.resolve().then(() => {
  new Error('error!!!')          // 只是生成了一个Error对象,并没有抛出错误,如果使用throw,后面的catch将捕获到并打印错误
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})
// then:  undefined
// throw抛出异常
Promise.resolve().then(() => {
  throw new Error('error!!!')   
}).then(res => {
  console.log("then: ", res)
}).catch(err => {              // catch捕获到错误
  console.log("catch: ", err)
})
// catch:  Error: error!!!
// 建议总是使用catch()方法,而不使用then()方法的第二个参数。


// ===============================================================================================================
// resolve传递丢失的情况
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('timer')
    resolve('success')
  }, 1000)
})
const start = Date.now();
r = promise.then(undefined, res => {   // 参数为undefined,表示不处理fulfilled的情况     
  console.log(res, Date.now() - start)
})
r1 = r.then(res => {
  console.log('r:', r);  
  console.log(res, Date.now() - start)
})
r2 = r1.then(res => {
  console.log('r1:', r1);  
  console.log(res, Date.now() - start)
})
console.log(r2)
// timer
// success 1013
// undefined 1013
/*
	1.当原始的Promise对象promiser的状态变为"fulfilled"时,promise.then的状态和值会受到影响。因为在promiser.then(onFulfilled, onRejected)中,我们传入了undefined作为第一个参数(即onFulfilled),表示不处理fulfilled的情况,所以promiser.then的状态将直接继承自原始Promise对象的状态。
	2.当原始Promise对象的状态已经是"fulfilled"时,promiser.then的状态也将变为"fulfilled",并且其值将与原始Promise对象保持一致。
*/
Promise.resolve('success').then().then().then((res)=>{console.log(res)}).then((res)=>{console.log(res)})
// success
// undefined 因为前第一个回调函数已经接收到了参数,所以没有拿到值
/*	
	1.当.then()方法没有传入任何参数时,它返回一个新的Promise对象。新的Promise对象与前一个Promise对象的状态和值是相互关联的。
	2.前一个Promise对象的状态为"fulfilled",新的Promise对象的状态也会是"fulfilled",并且它的值将和前一个Promise对象保持一致。
*/
Promise.resolve('success').then(()=>{}).then().then((res)=>{console.log(res)}).then((res)=>{console.log(res)})
// undefined
// undefined
/*
	即时回调函数没有取值,但是参数已经传递进去了,导致后面的then方法拿不到
*/

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
.then(console.log)
// 1
/*
扩充知识点:
    这里的参数console.log是一个函数,它会作为then()方法的回调函数来执行。
    console.log方法可以接收任意数量的参数,并将它们打印到控制台。
    当传递一个Promise对象作为参数时,console.log方法会按照以下步骤来打印Promise对象的值:
		1.console.log方法被调用,并接收Promise对象作为参数。
		2.console.log方法内部会检查传入的参数是否是一个Promise对象。如果是Promise对象,
			它会等待Promise对象的状态变为resolved(已完成)或rejected(已拒绝)。
        3.一旦Promise对象的状态变为resolved或rejected,console.log方法会获取Promise对象的值,并将其打印到控制台。
    所以,then(console.log) 会正常打印Promise对象的值,而then(console.log())会打印console.log函数执行的结果(通常是 undefined)作为回调函数的参数。	通常我们使用then(console.log)的方式来打印Promise对象的值。
*/

9) Promise.finally
  • finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
  • finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected

说明finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

  • 它最终默认返回的会是一个上一次的Promise对象的值,如果抛出的是一个异常则返回状态rejected的Promise对象。
  • 它也是创建的一瞬间就会产生一个promise对象,也需要代码执行完毕之后确定状态。虽然状态有一部分取决于之前的对象。
test = Promise.resolve().finally(()=>{console.log('1111111')})
console.log(test)
// Promise {<fulfilled>: undefined}

test = Promise.reject().finally(()=>{console.log('1111111')})
// Uncaught (in promise) undefined  Promise.reject报错
console.log(test)
// Promise {<rejected>: undefined}

test = Promise.resolve().finally(()=>{console.log('1111111');throw 111})
// 1111111
// Uncaught (in promise) 111   throw主动抛出的异常
console.log(test)
// Promise {<rejected>: 111} 这个状态是Promise.finally对象的的状态,
// 因为最终要确定finally的回调函数有没有报错或异常,有异常则返回状态rejected的Promise对象,状态的值为抛出的异常 

function promise1 () {                        // fulfilled
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {  // rejected
    reject('error')
  })
}
promise1()
  .then(res => console.log(res))            // fulfilled 
  .catch(err => console.log(err))           // fulfilled catch没有捕获到异常,它会正常返回一个fulfilled状态的Promise对象
  .finally(() => console.log('finally1'))   // fulfilled 回调函数执行后的状态

promise2()
  // 由于then方法没有第二个参数,直接继承自原始Promise对象的状态, 或者说它把设置当前then对象状态的函数放到微任务队列中去执行了
  .then(res => console.log(res))            // rejected 
  .catch(err => console.log(err))           // fulfilled catch捕捉到异常,返回一个成功态的状态
  .finally(() => console.log('finally2'))   // fulfilled 回调函数执行后的状态
// promise1 
// 1  
// error 
// finally1 
// finally2

10) Promise.all、Promise.race
  • all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
  • race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
// 小tip:大多数情况下为了控制它什么时候执行,我们可以用一个函数包裹着它,在需要它执行的时候,调用这个函数就可以了
// 比如这样:
function runP1 () {
    const p1 = new Promise(r => console.log('立即打印'))
    return p1
}
runP1()

// ================================================================================================================
// all()
function runAsync (x) {
    console.log('ready', x)
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), x*1000))
    return p
}
Promise.all([runAsync(3), runAsync(2), runAsync(1)])
  .then(res => console.log(res))
console.log('start')
// ready 3
// ready 2
// ready 1
// start
// 1
// 2
// 3
// [3, 2, 1]
/*
	1.Promise.all 接收的必须是一个可迭代的对象,且返回的每个成员都是 Promise 实例
	2.then()方法里的回调函数接收的参数就是所有异步操作的结果,是一个数组类型对象
	3.结果中的数组的顺序和Promise.all()接收到的数组顺序一致
    4.all后面的then()方法的状态取决于all的全部状态
    	all方法里边的数组的每个Promise对象都变成fulfilled,Promise.all的状态才会变成fulfilled
    	数组里边的promise对象只要有一个被rejected,Promise.all的状态就会变成fulfilled,而第一个被reject的Promise对象的返回值,会传递给promise.all对象的回调函数。
*/


// ================================================================================================================
// race()
function runAsync (x) {
    console.log('ready', x)
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), x*1000))
    return p
}
Promise.race([runAsync(3), runAsync(2), runAsync(1)])
  .then(res => console.log(res))
console.log('start')
// ready 3
// ready 2
// ready 1
// start
// 1  最先执行完的Promise对象的值
// 1
// 2
// 3
/*
	1.使用.race()方法,它只会获取最先执行完成的那个结果,其它的异步任务任然会继续执行
	2.那个率先改变的Promise对象状态的返回值,就传递给Promise.race的回调函数
*/

  • Promise.race()方法的参数与Promise.all()方法一样,如果不是Promise对象,就会先调用Promise.resolve()方法,将参数转为Promise对象,再进一步处理。
11) Promise.resolve
  • 有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

  • Promise.resolve()方法的参数分成四种情况:

​ 参考:es6.ruanyifeng.com/#docs/promi…

小tip:如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。

12) async 关键字

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

  • async函数返回一个 Promise 对象。
  • async函数内部return语句返回的值,会成为then方法回调函数的参数。
  • async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
  • async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
  • 正常情况下,await命令后面是一个Promise对象,返回该对象的结果。如果函数返回的是一个Promise对象,则表明函数涉及到异步操作,await下面的语句将被放到微任务队列(前提是await语句没有报错,并且返回一个成功态的Promise对象);如果不是Promise对象,就直接返回对应的值
  	    * `await`命令后面的`Promise`对象,运行结果可能是`rejected`,所以最好把`await`命令放在`try...catch`代码块中

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法
async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}


// 多个await命令后面的异步操作,如果不存在继发关系(不相互依赖,不按照一定顺序执行),最好让它们同时触发。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
// 上面两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。

 * 如果`await`后面的异步操作出错,那么等同于`async`函数返回的 Promise 对象被`reject`。
 * 任何一个`await`语句后面的 Promise 对象变为`reject`状态,那么整个`async`函数都会中断执行。

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')
// async1 start
// async2
// end
// async1 end
/*
执行步骤:
	1.首先一进来是创建了两个函数的,我们先不看函数的创建位置,而是看它的调用位置
    2.发现async1函数被调用了,然后去看看调用的内容
    3.执行函数中的同步代码打印async1 start,之后碰到了await,它会阻塞async1后面代码的执行,因此会先去执行async2中的同步代码打印	async2,然后跳出async1
    4.跳出async1函数后,执行同步代码start
    5.在一轮宏任务全部执行完之后,再来执行刚刚await后面的内容打印async1 end。
*/

 
// 经典案列:
// eg1
async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  setTimeout(() => {
    console.log('timer')
  }, 0)
  console.log("async2");
}
async1();
console.log("start")
// async1 start
// async2
// start 
// async1 end
// timer

// eg2
async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('timer1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timer3')
}, 0)
console.log("start")
// async1 start
// async2
// start
// async1 end
// timer2
// timer3
// timer1


// async 本质上是一个Promise对象
// 1.正常情况下,async中的await命令是一个Promise对象,返回该对象的结果。
// 2.如果不是Promise对象的话,就会直接返回对应的值,相当于Promise.resolve()
async function fn () {
  // return await 123
  // 等同于
  return 123
}
fn().then(res => console.log(res))
// 123  
// 由于fn同步执行的时候,直接返回的是await命令,而await命令返回的是一个原始值,不是一个Promise对象;
// 所以会返回一个新的Promise对象,状态为resolved,值为原始值。原始值的参数会同时传给回调函数。

// eg3
async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {         // pending 状态的Promise对象
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
// srcipt start
// async1 start
// promise1
// srcipt end
/*
执行步骤:
	同步执行代码遇到await命令,遇到一个pending状态的Promise对象,await命令下面的代码不能放到微任务队列中;
	async1异步函数执行完毕是一个pending状态的promise对象,所以async1().then回调函数不会执行。
*/

// eg4
async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise1 resolve')              // 成功态的promise对象
  }).then(res => console.log(res))           // 加入微任务队列
  console.log('async1 success');             // 加入微任务队列
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
// srcipt start
// async1 start
// promise1
// srcipt end
// promise1 resolve
// async1 success
// async1 end

//eg5
async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise resolve')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => {
  console.log(res)
})
new Promise(resolve => {
  console.log('promise2')
  setTimeout(() => {
    console.log('timer')
  })
})
// srcipt start
// async1 start
// promise1
// promise2
// async1 success
// async1 end
// timer

// eg6(面试题)
async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
console.log("script start");
setTimeout(function() {
  console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
  console.log("promise1");
  resolve();
}).then(function() {
  console.log("promise2");
});
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

// eg7
async function testSometing() {
  console.log("执行testSometing");
  return "testSometing";
}
async function testAsync() {
  console.log("执行testAsync");
  return Promise.resolve("hello async");
}
async function test() {
  console.log("test start...");
  const v1 = await testSometing();
  console.log(v1);
  const v2 = await testAsync();
  console.log(v2);
  console.log(v1, v2);
}
test();
var promise = new Promise(resolve => {
  console.log("promise start...");
  resolve("promise");
});
promise.then(val => console.log(val));
console.log("test end...");
// test start...
// 执行testSometing
// promise start...
// test end...
// testSometing
// 执行testAsync
// promise
// hello async
// testSometing hello async
// 得出结论Promise.resolve("hello async");和return "testSometing" 效果是一样的


// eg8
// 在async中,如果 await后面的内容是一个异常或者错误的话,简单看一下
// 如果在async函数中抛出了错误,则终止错误结果,不会继续向下执行。
async function async1 () {
  await async2();                 // 任何一个await语句后面的Promise对象变为reject状态,那么整个async函数都会中断执行。
  console.log('async1');          // 不会执行
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))      
// async2
// Uncaught (in promise) error

// eg9
// 如果想要使得错误的地方不影响async函数后续的执行的话,就要使用try...catch了
async function async1 () {
  try {
    await Promise.reject('error!!!')
  } catch(e) {
    console.log(e)
  }
  console.log('async1');
  return Promise.resolve('async1 success')
} 
async1().then(res => console.log(res))
console.log('script start')
// script start
// error!!!
// async1
// script start

// eg10
const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
            console.log(p)
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);
// 3
// 7
// 4
// 1
// 2
// 5
// Promise {<fulfilled>: 1}

// eg11
const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)


### 最后

技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

>技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

![](https://img-blog.csdnimg.cn/img_convert/0481144b34166491ea12242505dfb004.webp?x-oss-process=image/format,png)


// 不会执行
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))      
// async2
// Uncaught (in promise) error

// eg9
// 如果想要使得错误的地方不影响async函数后续的执行的话,就要使用try...catch了
async function async1 () {
  try {
    await Promise.reject('error!!!')
  } catch(e) {
    console.log(e)
  }
  console.log('async1');
  return Promise.resolve('async1 success')
} 
async1().then(res => console.log(res))
console.log('script start')
// script start
// error!!!
// async1
// script start

// eg10
const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
            console.log(p)
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);
// 3
// 7
// 4
// 1
// 2
// 5
// Promise {<fulfilled>: 1}

// eg11
const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)


### 最后

技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

>技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

[外链图片转存中...(img-BA6VwoFa-1715906980201)]


  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值