ES6的语法以及使用方法

1.概述:

ECMAScript、Javascript、Node.js之间的区别是什么。

ECMAScript:简称ES,是一个语言标准(循环,变量,判断,数组这些基本数据类型的构成标准。)

JavaScript:运行在浏览器端的语言,该语言使用的就是ES标准。ES+web api=JavaScript

NodeJs:运行在服务器端的语言,同上。ES+node api=nodejs

关键版本,es3.0 1999年 ,es5.02009年,es6.0 2015,从该年开始用年份代表。es4.0出了问题下架了

es6解决js无法开发大型应用的语言层面问题,是es6极其重要的原因

一、块级绑定

声明变量的问题

过去使用var 来声明变量

1.允许重复的变量声明:导致数据被覆盖

由var来进行定义变量容易重名,导致闭包。

2.变量提升;怪异的数据访问

由var声明的变量在函数中会产生变量提升导致逻辑十分的怪异,典型的有闭包问题,在循环过程中,由于变量提升,导致循环结束了,变量才进入函数

3.全局变量挂载到全局对象,全局对象成员污染问题。

var的变量如果赋值到window里已有的变量会导致全局对象原有的成员被污染。

var abc=“123”

console.log(window.abc);//会将变量abc挂到全局对象上

var console="abc";如果给全局对象上的东西赋值会导致原本console这个对象消失(污染)

console.log(console);

由于var存在上面的问题,所以需要引入块级绑定。

let声明的变量可以解决上面问题。

let声明的变量,不允许当前作用域范围内重复声明;不会挂载到全局,解决var的使用问题,引入块级作用域的概念,代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域。使用方法

if(Math.random()<0.5){

let a=123;//定义在当前作用域块

console.log(a)//当前块级作用域中的a,123

}

else{

//这是另外一个块级作用域,该作用域中找不到a

console.log(a),报错, a no define

}

console.log(a);,报错

局部的可以获取全部的,全部的不能获取局部的

在底层逻辑上,let声明实际上会提升,只不过提升后会将其放入到“暂时性死区”如果放到暂时性死区会报错。范例代码:

console.log(a);let a=123;

在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的都是一个全新的循环变量)

varbtns=document.getElementById("btn");
for (leti=0; i<10; i++) {
    varbtn=document.createElement("button");
    btn.innerHTML="按钮"+i;//btn为var声明的变量,所以下面也是点击btn
    btn.οnclick=function () {
        console.log(i);
    }
    btns.appendChild(btn)
}

使用const声明常量

const和let一样,必须在声明时赋值,不可以重新赋值,实际上,在开发中,多应用const声明变量,保证变量的值不会篡改。

根据经验,开发中的很多变量,都是不会更改,也不应该更改的。

后续的很多框架或者是第三方js库,都要求数据不可变,所以使用const

细节:

1.常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。

consta=1;
a=2;//报错,由于常量不可以改变,
constb={
    age : kevin,
    number : 123,
};
b.age=123;//可以修改,保存的是b本身,里面的内容可以改变

2.特殊的命名是不可变的,比如说一些定理之类的常量,就用const定义变量并且改成变量名使用大写,多个单词之间用下划线分割,普通的常量使用之前一样的命名规则

3.for循环当中不能使用const来定义常量(easy!)

更好的Unicode支持

早期,由于存储空间宝贵,unicode使用16位二进制来存储文字,我们将一个16位的二进制编码叫做一个码元(Code Unit),二的十六次方,不够存储文字。

后面,由于技术的发展,Unicode对文字编码进行了扩展,将某些文字扩展到了32位(占用两个码元),并且,将某个文字对应的二进制数字叫做码点(code point)。

es6解决了 码元和码点之间的矛盾,为字符串提供了方法:charCodePoint。

4.更多的字符串api

includes,startsWith,endsWith,repeat

includes判断字符串中包含指定字符串

startsWith判断字符串中是否以指定字符串开头

endsWith判断字符串中是否以指定字符串结尾

repeat把指定字符串重复n次

const text="强哥是狠人";
console.log('是否包含“狠”:',text.includes('狠'));
console.log('是否以成哥为开头:',text.startsWith('强哥'));
console.log("是否以狠人为结尾:",text.endsWith('狠人'));
console.log('重复4次:',text.repeat(4));

正则中的粘连标记:

标记名:y,

含义:匹配时,完全按照正则对象中的lastIndex位置开始匹配,并且匹配的位置 必须在lastIndex位置。

const text="Hello World!!!";
const reg=/W\w+/;//匹配以W为开头后面是任意的多个字符
console.log("reg.lastIndex:",reg.lastIndex);
console.log(reg.test(text))//true
const text1="Hello World!!!";
const reg1=/W\w+/y;//加y变为从lastindex位置开始匹配,即Hello位置开始匹配
console.log("reg1.lastIndex:",reg1.lastIndex);
console.log(reg1.test(text1))//false

如果修改lastIndex的位置也会改变结果,如果没有加粘连标志就会往后面的地方查找。

模板字符串:

es6之前处理字符串繁琐的两个方面:

1.多行字符串2.字符串拼接

在es6中,提供了模板字符串的书写,可以非常方便的换行和拼接,要做的,仅仅是将字符串的开始和结尾改为`符号

 换行:

var text=`邓哥喜欢秋葵

邓哥也喜欢韭菜`;直接进行换行,换行中最好不要加tab键,否则结果也会是带有tab键。

如果要在字符串中拼接js表达式,只需要在模板字符串中使用````${JS表达式}````

模板字符串的标记(开发不常见)暂时不学

函数:

1.参数问题:

书写形参时直接给形参赋值,赋的值即为默认值,这样一来如果没有给参数赋值(给他的值是undefined)则会自动赋为默认值,不能传null,null在数学中是0;如果想传三个参数,则第二个参数传undefined其余按需求来

functionsum(a, b =2, c =3) {
  returna+b+c;
}
console.log(sum(10, null, 3))//13
console.log(sum(10, undefined, 3))//15
/**
*
* @param{*}name 元素的名称
* @param{*}container 元素的父元素
* @param{*}content 元素的内容
*/
functioncreateElement(name ="div", container =document.getElementById("container"), content ="") {
  constele=document.createElement(name);
  if (content) {
      ele.innerHTML=content;
  }
  container.appendChild(ele);
}
createElement(undefined, undefined, "js");

参数默认值对arguments的影响:

只要给函数形参加上默认值,该函数会自动变成严格模式(即arguments和形参脱离)

留意暂时性死区:

形参和es6中的let和const声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区

functiontest(a=b,b){
  console.log(a,b);
}
test(undefined,2);//由于a传的是undefined,所以使用默认值,但是b尚未声明,存在暂时死区,所以会报错

剩余参数

考虑到传的参数可能有无限多个,对于函数来说只会设计一次,而调用函数的情况则是十分多的

arguments的缺陷:

1.如果和形参配合使用,容易导致混乱(严格模式下二者分离,否则其中一个改变另一个跟着改变)

2.从语义上,使用arguments获取参数,由于形参的缺失,无法从函数定义上理解函数的真实意义

es6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。

functionsum(...args) {
  letsum=0;
  for (leti=0; i<args.length; i++) {
      sum+=args[i];
  }
  return sum;
}
console.log(sum());
console.log(sum(1));
console.log(sum(1, 2));
console.log(sum(1, 2, 3));

语法:function(...形参名){}

细节:

一个函数仅能出现一个剩余参数;

一个函数如果有剩余参数,剩余参数必须是最后一个参数

展开运算符:

1.对数组展开(es6):

...要展开的数组(相比较剩余运算符只是位置不同)

functionsum(...args) {
  let sum=0;
  for (leti=0; i<args.length; i++) {
      sum+=args[i];
}
  return sum;
}
functiongetRandomNumbers(length) {
  constarr= [];
  for (leti=0; i<length; i++) {
      arr.push(Math.random());
  }
  returnarr;
}
constnumber=getRandomNumbers(10);
console.log(number);
//console.log(sum(number));//报错,因为传入的是一个数组(一个参数)而并非多个参数,循环不能执行
console.log(...number,1,2)//相当于传入十二个参数,参数的位置也比较灵活

constarr1=[3,7,8,5];
constarr2=[...arr1];
//克隆arr1到arr2中
console.log(arr2,arr2===arr1)//深度克隆

2.对对象展开(es7)

constobj={
  name:"lf",
  age:18,
  addr:{
      country:"china"
  }
}
constobj1={
  ...obj,
  name:"wwq",//会覆盖前面的
//     addr:{
//         ...obj.addr,//将其单独深克隆
//     }
}
console.log(obj1);
console.log(obj===obj1);//false,深度克隆
console.log(obj.addr===obj1.addr);//true,浅克隆,因为addr没有被克隆

柯里化(curry)

function cal(a,b,c,d){
  return a+b*c-d;
  }
  /**
  *
  
  ● @param  {*} cal 上面所设计的函数
  ● @param   {...any} args 固定的函数数量
  */
  function curry(func,...args){
  return function(...subargs){//返回一个新的函数(newcal),subargs是新函数所需的参数
  const allargs=[...args,...subargs];
  if(allargs.length>=func.length){//参数足够了
  return func(...allargs);
  }
  else{//参数不足
  return curry(func,...allargs);//继续固定
  }
  }
  }
  const newcal=curry(cal,1,2)//curry函数,前两位固定1,2
  console.log(newcal(3,4))
  console.log(newcal(5,6))
  console.log(newcal(4,5))
  console.log(newcal(6,7))
  // const newcal2=newcal(8);//newcal只传了一个参数不够
  // //curry:柯里化,用于固定某个函数前面的参数,得到一个新的函数,新的函数调用时,接收剩余参数
  // console.log(newcal2(9))//再传入一个参数,结果为1+2*8-9

明确函数的双重用途:

function Person(firstName, lastName) {
  //过去的判断方式
  //if(!(this instanceof Person)){
//throw new Error("该函数没有使用new来调用");
//}
if(new.target===undefined){
throw new Error("该函数没有使用new来调用");
}
  this.firstName = firstName;
  this.lastName = lastName;
  this.fullName = `${ firstName }${ lastName }`;
}
const p1 = new Person("小", "王");
console.log(p1);
const p2 = Person("小", "王");
console.log(p2);//undefined,因为没有使用构造函数,直接默认再window上,所以是undefined
const p3=Person.call(p1,"小',"王")
console.log(p3);//undefined,绕开了过去的判断方式

es6提供了一个特殊api,可以使用该api在函数内部,判断该函数是否使用了new来调用(构造函数)

·····js

new.target//该表达式,得到的是,如果没有用new来调用函数,则返回undefined;如果用new来调用,则得到的是new关键字后面的函数本身

········

箭头函数:

回顾this指向:

1.通过对象调用函数,this指向对象,

2.直接调用函数,this指向全局对象

3.如果通过new调用函数,this指向新创建的对象

4.如果通过apply、call、bind调用函数,this指向指定的数据

5.如果是DOM事件函数,this指向事件源。

使用语法:

箭头函数是一个函数表达式,理论上,任何使用函数表达式的场景都可以使用箭头函数

完整语法:

(参数1,参数2,...)=>{ //函数体

}

简单语法(只有一个参数):

参数=>{

//函数体

}

如果箭头函数只有一条返回语句,可以省略大括号,和return关键字

参数=>返回值,如果返回的是对象,应该使用()。

注意细节:

箭头函数中,不存在this,arguments,new.target,如果使用了,则使用的是函数外层的对应的this,arguments,new.target

箭头函数没有原型,所以箭头函数不能作为构造函数使用

应用场景:

1.临时性使用的函数,并不会刻意调用它比如:

 事件处理函数

异步处理函数(setTimeout,setInterval)

其他临时性的函数

2.为了绑定外层this的函数

3.在不影响其他代码的情况下,保持代码的简洁,最常见的就是数组中的回调函数

const number=[2,3,7,8,15,16];
const result=number.filter(num=>num%2!==0).map(num=>num*2).reduce((a,b)=>a+b);//使用箭头函数来保持代码简洁
//reduce把两两数字累计相加
console.log(result);

相关代码:

const obj={
  count:0,
  start:function(){
      setInterval(()=>{
          this.count++
          console.log(this.count);//this指向obj,因为使用箭头函数,this指向取决于位置
      },1000)
  },
  regEvent:function(){
      window.οnclick=()=>{
          console.log(this.count);//同上
      }
  },
  print:()=>{
      console.log(this.count);//window,因为this没有放在函数里面,它就是指向window的
      console.log(this);//window
  }
  //对于print他的表达形式是print:this?,在该位置this指向window
}
//如果没有使用箭头函数,立即执行函数相当于直接用函数调用this,指向全局对象;
//对于regEvent,由于用onclick函数,this指向事件源
//使用箭头函数,则与如何调用无关,而是与其所在位置有关联
// obj.start();
// obj.regEvent();
obj.print();

对于上面的print导致的问题我们一般不解决,因为开发过程中不会这样来使用this

// const isodd=function(num){
//     return num%2!==0;//以前的做法
// }
// const isodd=(num)=>{
//     return num%2!==0;//箭头函数用法
// }
const isodd=num=>num%2!==0;//箭头函数只有一条返回语句
console.log(isodd(3));
console.log(isodd(8));
-----------------------------------------------
const sum=(a,b)=>({
        a:a,
        b:b,
        sum:a+b,
})
console.log(sum(3,4));

新增的对象字面量语法

1.成员速写

如果对象字面量初始化时,成员的名称来自于一个变量,并且和变量的名称相同,则可以进行简写

function create(loginId, loginPwd, nickName) {
  return {
      loginId,
      loginPwd,
      nickName,
  }
}
console.log(create("abc", "123", "aaa"));

2.方法速写

对象字面量初始化时,方法可以省略冒号和function关键字

const obj={sub(){console.log(1)};

obj.sub()

3.计算属性名

有的时候,初始化对象时,某些属性名可能来自于某个表达式的值,在es6,可以使用中括号来表示该属性名是通过计算得到的

const prop1 = "name";
const prop2 = "age";
const prop3 = "sayHello";
const user = {
    [prop1]: "文强",//字面量书写
    [prop2]: 100,
    [prop3]() {
        console.log(this[prop1], this[prop2])
    }
}
console.log(user[prop1]);//原本的调用对象方法
user[prop3]();
console.log(user)

object(函数)的新增API

1.object.is

用于判断两个数据是否相等,基本上跟严格相等(===)是一致的,除了以下两点:

1)NaN和NaN是相等的

2)+0和-0是不相等的(由于+0和-0的二进制不一样)

console.log(NaN===NaN);//false

console.log(+0===-0);//true

//上面是历史遗留问题

只有上面两种情况需要用到object.is才用

2.object.assign

用于混合对象(现在不常用,直接用es7的展开运算符,后面展开的覆盖前面的)

3.object.getOwnPropertyNames的枚举顺序

这个方法之前就存在,只不过官方没有明确要求对属性的顺序怎么排序,如何排序完全由浏览器决定

es6规定了该方法返回的数组排序方式如下:

先排数字(按升序排序),再排其他(按照书写顺序排序)

4.object.setPrototypeof

该函数用于设置某个对象的隐式原型

比如object.setPrototypeof(obj1,obj2)

相当于:obj1.__proto__=obj2

面向对象简介:

面向过程的切入点是功能的步骤

面向对象的切入点是对象的划分

(典例:冰箱装大象)

面向过程主要是就过程来一步步进行编程,面向对象则是创建对象方法来进行编程

前者适用于小型功能的实现,后者适合于大型项目的实现,可维护性和可扩展性高

类:构造函数的语法糖

传统的构造函数的问题

1.属性和原型方法定义分离,降低了可读性

起初js没有考虑用面向对象来进行编程,导致属性和方法定义之间可能有很多的代码,降低了可读性

2.原型成员可以被枚举(会枚举到对象的原型链上的属性,我们是不希望出现这种情况的)

3.默认情况下,构造函数仍然可以被当作普通函数使用(需要使用new.target来判断是否使用new来调用)

//面向对象中,将下面对对象的所有成员的定义,统称为类
//构造函数
function Animal(type,name,age,sex){
  this.type=type;
  this.name=name;
  this.age=age;
  this.sex=sex;
}
//实义实例方法(原型方法)
Animal.prototype.Print=function(){
  console.log(`${this.type}`);
  console.log(`${this.name}`);
  console.log(`${this.age}`);
  console.log(`${this.sex}`);
}
const a=new Animal("dog","xl","21","male");
a.Print();

es6的类的语法:

class Animal {
  constructor(type, name, age, sex) {
      this.type = type;
      this.name = name;
      this.age = age;
      this.sex = sex;
  }
  //print函数被设置为原型链上的函数,不会被枚举
  print() {
      console.log(`${this.type}`);
      console.log(`${this.name}`);
      console.log(`${this.age}`);
      console.log(`${this.sex}`);
  }
}
const a = new Animal("dog", "xl", "21", "male");
a.print();
console.log(a);

es6类的特点

1.类声明不会被提升,与let和const一样,存在暂时性死区(在window里面找不到)

2.类中的所有代码均在严格模式下执行

3.类的所有方法都是不可枚举的

4.类的所有方法都无法被当作构造函数使用

5.类的构造器必须使用new来调用

类的其他书写方式

1.可计算的成员名

直接使用[变量名]来命名类名,就可以在不知道变量的值的情况下调用成员函数

2.getter和setter

Object.defineProperty可定义某个对象成员属性的读取和设置

getter和setter就是在类中定义函数,来对成员属性进行读取和设置,设置时往往收集参数,读取时往往返回参数

3.静态成员

构造函数本身的成员

使用static关键字定义的成员(属性或者方法都可以使用这种写法)

4.字段初始化器(ES7)

注意:

1).使用static的字段初始化器,添加的是静态成员

2)没有使用static的字段初始化器,添加的成员位于对象上

3)箭头函数在字段初始化器位置上,指向当前对象,如果使用箭头函数会额外占用内存空间,应适当使用

5.类表达式

const A = class {//匿名类
  a = 1;
  b = 2;
}
const a = new A();
console.log(a);

类的继承

如果有两个类A和B,如果可以描述为:B是A,则A和B形成继承关系

如果B是A,则:

1.B继承自A

2.A派生B

3.B是A的子类

4.A是B的父类

继承关系表示,B将拥有A的所有实例成员

对于继承关系来说,B的原型链必须指向A的原型链,否则不算继承关系

旧版的继承关系代码:

function Animal(type, name, age, sex) {



  this.type = type;
  this.name = name;
  this.age = age;
  this.sex = sex;
}
Animal.prototype.Print = function () {
  console.log(`${this.type}`);
  console.log(`${this.name}`);
  console.log(`${this.age}`);
  console.log(`${this.sex}`);
}
function Dog(name, age, sex) {
  //借用父类的构造函数
  Animal.call(this, "犬类", name, age, sex);
}
//修改dog的原型链到animal上,如果缺少这条代码,则dog原型链指向object,无法形成继承
Object.setPrototypeOf(Dog.prototype, Animal.prototype);
const d = new Dog("xl", 18, "male");
d.Print();
console.log(d);

新的关键字:

extends:继承,用于类的定义

super:直接当作函数调用,表示父类构造函数

直接当作对象调用,表示父类的原型

es6的继承写法:

class Animal {
  constructor(type, name, age, sex) {
      this.type = type;
      this.name = name;
      this.age = age;
      this.sex = sex;
  }
  //print函数被设置为原型链上的函数,不会被枚举
  print() {
      console.log(`${this.type}`);
      console.log(`${this.name}`);
      console.log(`${this.age}`);
      console.log(`${this.sex}`);
  }
}
class dog extends Animal {
  constructor(name, age, sex) {
      super("犬类", name, age, sex);
  }
}
const d = new dog("xl", 18, "male");
d.print();
console.log(d);

注意:ES6要求,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类的构造函数

undefined如果子类不写constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器,如果父类的参数大于传输的参数,则没有传输的属性默认为undefined

子类单独的属性可以另外调用,调用出来时会自动填充父类没有的属性

如果在子类当中除父类所有的属性之外还有自己特有的属性,可以用super.父类的属性名()来复制父类函数中原有的,再添加自己要添加的

【冷知识】

-用js制作抽象类

-抽象类:一般是父类,不能通过new父类来进行创建对象

-正常情况下,this的指向始终指向具体的类的对象,即this指向新创建的对象

对象解构:

解构不会影响原始对象

什么是解构

使用es6的一种语法规则,将一个对象或数组的某个属性提取到某个变量中

先定义变量,然后从对象中读取同名属性,放到变量中,如果定义的变量没有同名属性则是undefined,语法:

let{name,age,sex,address}=user;

console.log(name,age,sex,address)

在解构中使用默认值(找不到的同名属性可以赋值为默认值)

{同名变量=默认值}

非同名属性解构

//先定义4个变量:name,age,gender,address

//再从对象user中读取同名属性赋值(其中gender读取的是sex属性)

let {name,age,sex:gender,address}=user

console.log(name,age,gender,address)

进一步解构:

//定义两个变量,name和province,province是对address的进一步解构,找不到address变量

const{name,address:{province}}=user;

console.log(name,province);

数组解构

const number=["a","b","c","d"];

const[n1,n2]=number//数组解构

console.log(n1,n2)//a,b

const [n1,,,n4]=number

console.log(n1,n4)//a,d

const numbers=["a","b","c","d",{

a:1,

b:2

}];

const[,,,,{a:A}]=numbers;//const{a:A} =numbers[4]效果相同

console.log(A)//1

用展开运算符解构剩余项

//解构出name,然后其他属性,放到一个新的对象当中,变量名为obj

const{name,...obj}=user

解构还能用于交换数值

let a=1,b=2;

[b,a]=[a,b];

参数解构

function ajax({

method="get",

url="/abc"

}={}){

console.log(method,url)

}

ajax()//没有传参数时,在解构时要添加一对大括号

function print({name,age,sex,address:{
  province,city
}}){
  console.log(`${name}`);
  console.log(`${age}`);
  console.log(`${sex}`);
  console.log(`${address}`);
  console.log(`${province}`);
  console.log(`${city}`);
}
const user={
  name:"kevin",
  age:11,
  sex:"男",
  address:{
      province:"四川",
      city:"成都",
  }
}

普通符号

符号是es6新增的一个数据类型,它通过调用函数 Symbol(符号名)来创建

符号设计的初衷,是为了给对象设置私有属性

私有属性:只有在对象内部使用,外面无法使用

符号的特点:

没有字面量

符号的typeof为symbol

每次调用symbol函数得到的符号永远不相等,无论符号名是否相同,符号都是独一无二的

符号可以作为对象的属性名存在,这种属性称之为符号属性,可以使得该属性在外部无法被访问

符号属性是不能呗枚举的,因此在for in循环中无法读取到符号属性,object.keys也无法读取到

es6新增的object.getownpropertynames可以读取到符号

符号是不能隐式转换的,但是它可以显式的转换为字符串,通过string构造函数进行转换即可,console.log之所以可以输出符号,是它在显式转化为字符串

共享符号

根据某个符号名称(符号描述)能够得到同一个符号

如果代码在同一个文件即可直接使用符号来构建函数

Symbol.for(“符号名/符号描述”)//获取共享符号

知名(公共、具名)符号

在某些场景下可以参与js内部的实现

A[Symbol.hasInstance](obj)//Function.prototype[Symbol.hasInstance]

异步处理

事件循环

js运行的环境称为宿主环境

执行上下文的定义:

当代码运行时,会产生一个对应的执行环境,在这个环境中,所有变量会被事先提出来(变量提升),有的直接赋值,有的为默认值 undefined,代码从上往下开始执行,就叫做执行上下文。

运行环境由下面三种不同的代码类型确定:

全局代码(Global Code):代码首次执行时候的默认环境

函数代码(Function Code):每当执行流程进入到一个函数体内部的时候

Eval代码(Eval Code):当eval函数内部的文本执行的时候

执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境

JS引擎永远执行的是执行栈的最顶部。

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。

浏览器宿主环境中包含5个线程:

  1. JS引擎:负责执行执行栈的最顶部代码
  2. GUI线程:负责渲染页面
  3. 事件监听线程:负责监听各种事件
  4. 计时线程:负责计时
  5. 网络线程:负责网络通信

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  • 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
  • 微任务(队列):MutationObserver,Promise产生的回调进入微队列

MutationObserver用于监听某个DOM对象的变化

当执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。

promise(解决回调地狱)

promise规范:

 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在JS中应该表现为一个对象,该对象称之为Promise对象,也叫做任务对象

每个任务对象,都应该有两个阶段、三个状态

 

  1. 根据常理,它们之间存在以下逻辑:
    • 任务总是从未决阶段变到已决阶段,无法逆行
    • 任务总是从挂起状态变到完成或失败状态,无法逆行
    • 时间不能倒流,历史不可改写,任务一旦完成或失败,状态就固定下来,永远无法改变

 挂起->完成,称之为resolve挂起->失败称之为reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因。

 可以针对任务进行后续处理,针对完成状态的后续处理称之为onFulfilled,针对失败的后续处理称之为onRejected

 

promise的链式调用

catch方法

.catch(onRejected) = .then(null, onRejected)new Promise((resolve, reject) => {reject(new Error('abc'));}).catch((err) => {console.log('失败了!!', err);});

链式调用

 

  1.  then方法必定会返回一个新的Promise可理解为后续处理也是一个任务 

const pro1 = new Promise((resolve, reject) => {
  console.log('学习');
  resolve();
});
const pro2=pro1.then(()=>{
  console.log("考试");
})

  1.  新任务的状态取决于后续处理:
    •  若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的数据
    •  若有后续处理但还未执行,新任务挂起。
    •  若后续处理执行了,则根据后续处理的情况确定新任务的状态
      • 后续处理执行无错,新任务的状态为完成,数据为后续处理的返回值
      • 后续处理执行有错,新任务的状态为失败,数据为异常对象
      • 后续执行后返回的是一个任务对象,新任务的状态和数据与该任务对象一致

const pro1 = new Promise((resolve, reject) => {
  console.log('学习');
  resolve();
});
const pro2 = pro1.then(() => {
  return new Promise((resolve, reject) => {});
});
setTimeout(() => {
  console.log(pro2);
}, 1000);

这个例子模拟最后一个特点,返回promise对象,并且新任务的对象没有执行,所以promise2是挂起对象

由于链式任务的存在,异步代码拥有了更强的表达力

练习题见vscode8-2Promise的链式调用

// 常见任务处理代码

/*
 * 任务成功后,执行处理1,失败则执行处理2
 */
pro.then(处理1).catch(处理2)//因为成功之后进行处理一,而没有对失败进行处理,如果失败直接转到处理
//2

/*
 * 任务成功后,依次执行处理1、处理2
 */
pro.then(处理1).then(处理2)

/*
 * 任务成功后,依次执行处理1、处理2,若任务失败或前面的处理有错,执行处理3
 */
pro.then(处理1).then(处理2).catch(处理3)

# Promise的静态方法

| 方法名 | 含义 |

| ---------------------------- | ------------------------------------------------------------ |

| Promise.resolve(data) | 直接返回一个完成状态的任务 |

| Promise.reject(reason) | 直接返回一个拒绝状态的任务 |

| Promise.all(任务数组) | 返回一个任务<br/>任务数组全部成功则成功<br/>任何一个失败则失败,有一个挂起就是挂起 |

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值