ES6专栏

284 阅读7分钟

一、ES6知识点总结

1. 模板字符串

${}里:
  • 可以放:变量、算术计算、三目、对象属性、创建对象、调用函数、访问数组元素;即有返回值的合法的表达式
  • 不能放:没有返回值的js表达式,也不能放分支/判断、循环等程序结构。比如:if else for while...
var uname="丁丁";
console.log(`Welcome ${uname}`); // Welcome 丁丁
var sex=1;
console.log(`性别:${sex==1?"男":"女"}`);//性别: 男
var price=12.5;
var count=5;
console.log(`
单价:¥${price.toFixed(2)}  
数量:${count}
========================
小计:¥${(price*count).toFixed(2)}
`)// 单价:¥12.5,数量:5,小计:¥62.5
//复习第一阶段Date部分
var orderTime=1630549654731;//new Date().getTime()
console.log(`下单时间:${new Date(orderTime).toLocaleString()}`);
var arr=["日","一","二","三","四","五","六"];
// 0 1 2 3 4 5 6
//getDay()0 1 2 3 4 5 6
var day=new Date().getDay();
console.log(`今天星期${arr[day]}`);// 今天星期几

2. let

1)、var的问题
  • a. 会被声明提前
  • ——打乱程序的执行顺序
  • b. 没有“块级作用域”
  • ——代码块内的变量会超出代码块范围,影响外部变量
2)、什么是块级作用域
  • (js中没有,其他语言才有)
  • 指除了对象{}和function的{}之外,其余if else、for等分支和循环结构的{}范围在其他语言中成为块级作用域
  • 其他语言中,程序块{}内的变量,出了所在的程序块{},就不能使用
比如其他语言中:

image.png

JS中:
for(var i=0;i<arr.length;i++){
    ...
}
console.log(i);//正常使用
3)、let的优点
  • a. 不会被声明提前
  • ——保证程序正常执行
  • b. 让程序块,也变成了“块级作用域”。
  • ——保证块内的变量,不会影响块外的变量
4)、let的本质:
  • let底层会被翻译为匿名函数自调:
(function(){
    
})()
  • 在js底层当中并没块级作用域,而let的底层就相当于匿名函数自调生成的临时作用域
5)、let的三个小脾气:
  • a. 因为不会被声明提前,所以不能在声明变量之前,提前使用该变量

image.png

  • a.1: let底层就相当于匿名函数自调 image.png

  • b. 在相同作用内,禁止声明两个同名变量 比如:使用var定义变量赋值时,会将变量存储在window全局对象中,再次对该变量赋值会在全局对象中修改该对象的值;而使用let定义变量时不会保存在window

image.png

  • c. 因为let底层相当于匿名函数自调,所以,即使在全局创建的let变量,在window中也找不到
  • c.1: var定义的所有全局变量都保存在window

image.png

  • c.2 用let代替var后
let d = 10;
console.log(d); // 10
console.log(window.d);// undefined

image.png

  • c.3 原理:let底层相当于匿名函数自调是因为let能形成闭包

image.png

总结:varletconst区别

image.png

3. 箭头函数

  • 箭头函数重要特征:使函数内的this与函数外的this保持一致
  • 箭头函数也是函数作用域:因为箭头函数只让this,指向外部作用域的this,而箭头函数内的局部变量,依旧只能在箭头函数内使用。出了箭头函数不能用!所以,箭头函数依然有作用域!只不过影响this而已!不影响局部变量
  • 箭头函数底层相当于.bind(),永久绑定外部this
  • call无法替换箭头函数中的this
注意:不能使用箭头函数的地方/箭头函数缺点
  • 构造函数不能用
  • 对象的方法不能用
  • 原型对象方法不能用
  • DOM中事件处理函数不能用
  • 箭头函数无法用call,apply,bind改变this
  • 箭头函数不支持arguments(类数组对象)
  • 箭头函数没有prototype

4. for of

  • a. 遍历数字下标的数组或类数组对象
  • a.1 普通for循环既可以遍历索引数组,又可以遍历类数组对象(arguments)——只要下标是数字;但是没有简化空间

image.png

  • a.2 forEach 可以配合ES6箭头函数,很简化;但是只能遍历数字下标的数组,无法遍历类数组对象

image.png

  • a.3 只要遍历数字下标的东西,都可用for of 代替普通for循环和forEach

image.png

  • b. for of的问题
  • b.1 无法获得下标位置i,只能获得元素值
  • b.2 无法控制遍历的顺序或步调,只能从头到尾,一个挨一个的顺序遍历
  • b.3 无法遍历下标名为自定义下标的对象和关联数组
  • 但是,因为绝大多数数组都是数字下标,绝大多数循环都是从头到尾,一个挨一个遍历的,且绝大多数循环不太关心下标位置,只关心元素值,所以for of将来用的还是非常多的!
比较:for, forEach, for of, for in

image.png

总结:

  • 下标为数字,首选for of
  • 下标为自定义字符串,首选for in

5. 参数增强

(1)、参数默认值(default)

  • 问题1:
    • 调用函数时,如果不传入实参值,虽然语法不报错,但是形参默认接住undefined
    • undefined 极容易造成程序错误
  • 解决1:参数默认值
    • 今后,只要希望即使不传入实参值时,形参变量也有默认值使用时,就为形参指定默认值
    • function 函数名(形参1=默认值1,形参2=默认值2, ...){ }
    • 调用函数时,给形参传了实参值,则首选用户传入的实参值。如果没有给形参传是实参值,则形参默认启用=右边的默认值。
  • 问题2:
    • 参数默认值只支持没有实参值和连续更换开头参数值的情况
    • 不支持,跳过开头形参,只更换中间某个参数的情况。
    • 比如:function order( stapleFood="香辣鸡腿堡",streetFood="辣翅", drink="可乐“ ){ … }
      • 只想换小吃,其它两个保持默认
      • order(,"鸡米花") // 换中间,不支持
      • order(undefined,"鸡米花") // 不好
  • 解决2:参数解构
function order({stapleFood="香辣鸡腿堡",streetFood="辣翅", drink="可乐"}){
    console.log(` 您点的套餐是: 主食:${stapleFood}, 小吃:${streetFood}, 饮料:${drink}`)
}
order({undefined, streetFood:"鸡米花", undefined})

(2)、 展开运算符(spread)

  • a. 问题: 获取一个数组中的最大值
    • 虽然 Math.max(),可获得多个数中的最大值: Math.max(1,7,2,5) 返回 7
    • 但是 Math.max()不支持数组
        var arr=[1,7,2,5];
        Math.max(arr) //返回NaN
      
  • a.解决:先拆散数组,再传参
    • 不好的方法:apply; var arr=[1,7,2,5]; Math.max.apply(替换this的对象, 要拆散的数组)

    • 但是,本例中,与替换this无关,只想拆散数组,所以: 第一个实参值乱写,写什么都行

      image.png image.png

    • 缺点:不替换this,也被迫传一个对象


    • 好的方法:ES6 展开运算符”…”
      • 今后,只要希望单纯拆散数组,都用...展开运算符,只有即替换this,又要拆散数组时,才用apply
      var arr=[1,7,2,5]; 
      Math.max(…arr);
      

总结:...

  • 1)、定义函数时,形参列表中的...表示搜集,
  • 2)、调用函数时,实参列表中的...表示拆散

举例:

  • 1)、复制一个数组
var arr1=[1,2,3]; 
//复制arr1中所有元素保存到新数组arr2中 
var arr2=[...arr1];
  • 2). 合并多个数组和元素:
var arr1=[1,2,3]; 
var arr2=[5,6,7]; 
//拼接多个数组和多个元素为一个新的数组 
var arr3=[...arr1,4,...arr2,8];
  • 3). 克隆一个对象
var lilei={ sname:"Li Lei", sage:11 }; 
//克隆lilei 
var lilei2={...lilei};
  • 4). 合并多个对象和属性:
var obj1={ x:1, y:2 }; 
var obj2={ i:4, j:5 }; 
var obj3={...obj1, z:3, ...obj2, k:6};

6.解构(destruct:)

三种情况:

  • 数组解构:
    • 从一个复杂的数组中只提取出需要的个别元素单独使用
    let arr= [2022, 2, 14, 14, 5]
    //仅提取出arr数组中年、月、日三个值单独使用
    let [y, m, d] = arr
    console.log(`今年是${y}年`); 
    console.log(`本月是${m}月`); 
    console.log(`今天是${d}号`);
    
  • 对象解构:
    • 从一个大的对象中只提取出个别属性值单独使用
    const lilei={ sname:"Li Lei", sage:11 } //想提取出sname和sage单独使用
    let {sname:sname, sage:sage} = lilei
    //每当对象中:左边的属性名和:右边的变量名刚好相同时,其实只写一个名字即可
    let {sname, sage} = lilei
    console.log(`姓名:${sname}`) //Li Lei 
    console.log(`年龄:${sage}`) //11
    
  • 参数解构:
    • 问题: 单靠参数默认值,无法解决任意一个形参不确定有没有的情况。
    • 解决: ES6简写+参数默认值

二、class & 继承

Ⅰ、class

1、什么是class: 程序中专门集中保存一种类型的所有子对象的统一属性结构和方法定义的程序结构。

2、如何定义class,三句话:

  • 1)、用class包裹原构造函数 + 原型对象方法

  • 2)、原构造函数名升级为整个class的名字,所有构造函数 统一更名为"constructor"

  • 3)、原型对象中的方法,不用再加prototype前缀,也不用=function,直接简写为: 方法名(){...}

    image.png

    image.png

3、如何使用 class:

  • 和使用旧的构造函数完全一样:var 对象名=new class名(属性值,...);

4、ES6 class中 问题:

  • 虽然直接在class中定义的方法,都默认保存在原型对象中。

  • 但是直接在class中定义的属性,却不会成为共有属性,不会保存在原型对象中。

  • 而是成为每个子对象的自有属性。

image.png

image.png

5、解决方法:静态属性

  • 为了和其它主流开发语言尽量一致,ES6的class放弃了在原型对象中保存共有属性的方式。
  • 而是改为用静态属性保存!
  • 什么是静态属性:
    • 不需要创建子对象,单靠类型名就可直接访问的属性,就称为静态属性
  • 今后,只要如果希望所有子对象,都可使用一个共同的属性值时,都要用静态属性代替原来的原型对象属性

6、如何定义 静态属性

  • class 类型名{ static 共有属性名=属性值 ... ... }
  • 原理:
    • 标有static的静态属性,都是保存在构造函数对象身上。
    • 因为构造函数在程序中不会重复!
    • 所以,静态属性,也不会重复!
    • 任何时候,任何地点,访问一个类型的静态属性,永远 访问的都是同一份

image.png

image.png

7、如何访问静态属性:

  • 坑: 错误: this.静态属性
  • 正确:类型名.静态属性

image.png

8、static className到底存哪儿了?

image.png

9、class Student到底是什么? 相当于构造函数的别名

image.png

  • console.log(typeof Student); // function
  • 说明class只是个外壳,其本质还是普通的构造函数function

Ⅱ、继承

1、两种类型间的继承:

image.png

当定义的类型出现相同的属性时,抽离相同的属性形成父级class

2、解决:两种类型间的继承: 两大步

  • 1). 额外创建一个父级class
    • i. 父级class的构造函数中包含子类型class中相同部分的属性结构定义

    • ii. 父级class的原型对象中包含子类型class中相同部分的方法定义

    • iii. 既然父级class中保存了相同的属性结构和方法定义,则子类型class中,就可以删除所有重复的属性结构和方法定义

  • 2). 让子类型class继承父类型的class,2步:
    • i. 设置子类型的原型对象继承父类型的原型对象:class 子类型 extends 父类型{ ... } 原理:
      • 只是设置子类型的原型对象继承父类型的原型对象,只能保证孙子对象可以使用爷爷类型原型对象中的共有方法!暂时无法为孙子对象补全缺少的自有属性

image.png

4、问题1: extends?

  • inherit是继承的意思

  • 但是,为什么用extends表示继承

  • 答: 继承是为了更好的扩展

    • 程序中的继承,都是为了在继承现有成员的基础上,进一步扩展出自己个性化 的新成员!

    • 所以,程序中的继承,都用extends(扩展)

5、问题1: super?

  • 为什么指向父类型构造函数的关键字,称为super?

  • 答: super在数学中指超集

  • 比如:所有的猫,是一个集合;所有的老虎,也是一个集合

  • 但是,我们还可以用一个更大的集合“猫科动物”,来包含猫集合和老虎集合。

  • 这个可以包含猫集合和老虎集合的更大的集合“猫科动物”,就称为超集,英文为 super

image.png

6、程序中 总结:

  • 敌机类型Plane是一个集合

  • 降落伞类型San也是一个集合

  • 但是,可以用更大的一个集合敌人Enemy,来概括所有的敌机和降落伞。

  • 所以,敌人Enemy集合,就称为敌机类型和降落伞类型的超集super,程序中, 也称为超类

  • 所以: 程序中用super指父类型构造函数

三、promise

1、Promise 的含义

  • Promise 是异步编程的一种解决方案,用来防止回调地狱,以及让多个异步任务顺序执行

  • Promise对象有以下两个特点

    • 对象的状态不受外界影响。且有且只有三种状态
      • pending(进行中)

      • fulfilled(已成功

      • rejected(已失败)

    • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
      • Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected

2、基本用法

  • Promise对象是一个构造函数,用来生成Promise实例。
  • 例如:
const promise = new Promise((resolve, reject)=>{
  // ...
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
  • resolve函数的作用是: 异步函数成功时的回调。 即 pending -> fulfilled 状态的过程
  • reject函数的作用是: 异步函数失败时的回调。 即 pending -> rejected 状态的过程
  • Promise新建后就会立即执行。
let promise = new Promise(function(resolve, reject) {
  // 直接写在new Promise中的代码属于主程序,立即执行的
  console.log('a'); // 主程序,立即执行
  resolve(); // 自动调用.then()中的代码
});

promise.then(()=>console.log('b')); // 微任务 回调

console.log('c');// 主程序,立即执行

// a
// c
// b
  • 注意,调用resolvereject并不会终结 Promise 的参数函数的执行。想要终结程序必须在resolvereject前面加上return

    image.png

3、Promise.prototype.then()

  • then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数

  • 使用then()方法

    image.png

  • then()方法可以链式调用

    image.png

  • 如果函数抛出错误或返回一个拒绝的Promise,则 then 将返回一个拒绝的Promise。

    image.png

4、Promise.prototype.catch()

  • Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
  var p1 = new Promise(function(resolve, reject) {
    resolve('Success');
  });
  p1.then(function(value) {
    console.log(value); // "Success!"
    return Promise.reject('oh, no!');
  }).catch(function(e) {
    console.log(e); // "oh, no!"
  }).then(function(){
    console.log('catch捕获错误之后,链条恢复');
  }, function () {
    console.log('因为catch捕获了错误信息,导致失败的回调函数未被执行');
  });
// 也可以使用reject将错误抛出
 var p1 = new Promise(function(resolve, reject) {
    reject('error')
 });
 p1.catch((err)=>{
     console.log(err)// error
 })

一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

// bad
promise.then(function(data) {
  // success
}, function(err) {
  // error
});

// good
promise.then(function(data) { //cb
  // success
})
.catch(function(err) {
  // error
});
  • 捕获抛出的错误
// 抛出一个错误,大多数时候将调用catch方法
var p1 = new Promise(function(resolve, reject) {
 throw 'Uh-oh!';
});

p1.catch(function(e) {
 console.log(e); // "Uh-oh!"
});

// 在异步函数中抛出的错误不会被catch捕获到
var p2 = new Promise(function(resolve, reject) {
 setTimeout(function() {
   throw 'Uncaught Exception!';
 }, 1000);
});

p2.catch(function(e) {
 console.log(e); // 不会执行
});

// 在resolve()后面抛出的错误会被忽略
var p3 = new Promise(function(resolve, reject) {
 resolve();
 throw 'Silenced Exception!';
});                          

p3.catch(function(e) {
  console.log(e); // 不会执行
});

5、Promise.prototype.finally()

  • finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

  • finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

  • 实现原理:

image.png

上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback

从上面的实现还可以看到,finally方法总是会返回原来的值。

image.png

6、Promise.all()

  • Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

  • 适用于多个异步任务同时执行,但是只有在最后一个任务执行完才做一件事情

    • 错误:顺序调用多个异步任务,在最后执行想要执行的代码
    • 因为:主程序中的代码绝对不会等异步任务执行完才执行。一定是,多个异步任务刚开始执行时,主程序的代码就会抢先执行!
    • 正确:Promise.all([格子间1, 格子间2, 格子间3, ...]).then(最后一项任务)
    • 原理:
      • a、all后的数组中每个格子间的异步任务都是并发执行的!谁也不等谁!
      • b、但是,all后的.then()注定会等最慢的一个格子间执行完,才自动执行!
  • 举例:希望三个人百米赛跑(谁也不等谁),但是最后一个到达终点时,才输出比赛结束

function ming(){
    return new Promise((resolve)=>{
        console.log(`ming起跑。。。`);
        setTimeout(()=>{ // 异步:在主程序之外
            console.log(`ming到达终点!`); // 明最后执行的一句话
            //之后,主动调用 resolve
            resolve(); // 自动执行.then()中串联的下一项任务
        }, 6000)
    })
}
function hong(){
    return new Promise((resolve)=>{
        console.log(`hong起跑。。。`);
        setTimeout(()=>{ // 异步:在主程序之外
            console.log(`hong到达终点!`); // 红最后执行的一句话
            //之后,主动调用 resolve
            resolve(); // 自动执行.then()中串联的下一项任务
        }, 4000)
    })
}
function tian(){
    return new Promise((resolve)=>{
        console.log(`tian起跑。。。`);
        setTimeout(()=>{ // 异步:在主程序之外
            console.log(`tian到达终点!`); // 天最后执行的一句话
            //之后,主动调用 resolve
            resolve(); // 自动执行.then()中串联的下一项任务
        }, 2000)
    })
}

// 希望三个人百米赛跑(谁也不等谁),但是最后一个到达终点时,才输出比赛结束
//错误:
    // ming();
    // hong();
    // tian();
    // console.log(`比赛结束`);

//正确:
Promise.all([
    //只有调用异步函数,才能返回new Promise()格子间
    ming(), // return new Promise()
        //resolve()
    hong(), // return new Promise()
        //resolve()
    tian()// return new Promise()
        //resolve()
]).then(()=>console.log(`比赛结束!`))

错误的输出结果

image.png

正确的输出结果

image.png

Promise.all()的返回值:

a、接:Promise.all([...]).then(function(arr){...}), 此时arr接住所有all()中数组的值

b、all中接到的返回值的顺序:和all中异步函数的顺序一致,与执行结束的先后顺序无关!

function ming(){
    return new Promise((resolve)=>{
        let bang = 'ming的接力棒';
        //...与上面一致
    })
}
function hong(){
    return new Promise((resolve)=>{
        let bang = 'hong的接力棒';
        //...与上面一致
    })
}
function tian(){
    return new Promise((resolve)=>{
        let bang = 'tian的接力棒';
        //...与上面一致
    })
}
Promise.all([
   //...与上面一致
]).then((arr)=>{
    console.log(`比赛结束!`)
    console.log(arr); //三个人返回的三个接力棒
    // arr中接力棒的顺序是按照all中数组的顺序,还是按照异步函数结束的顺序?
})

image.png

四、async/await

1、async/await目的:

async和await 其实就是promise.then()的简写 目的是彻底消除嵌套

2、调用 promise函数时 的.then都可简 写为asyncawait的组合。

ming().then(hong).then(tian) 
//可改为: 
(async ()=>{ 
    let bang = await ming(); 
    bang = await hong(bang); 
    tian(bang); 
})()
    1. 只有基于Promise的函数,才支持async和await
    1. await必须用在被async标记的函数内
    1. 外层函数必须用async标记。目的是告诉主程序,这段函数内的 代码整体是异步执行的。不影响主程序的执行。
    1. await必须写在前一项任务之前
    1. await的作用等效于.then(),用来通知程序必须等待前一项任务执 行完,才能继续执行后续任务。
    1. 一旦使用了await,前一项任务的resolve(返回值),可以像普通函 数一样用=接住。后续代码可继续使用该变量里获得的返回值。
    1. await和.then()一样,可多次使用。控制多个异步任务顺序执行。

所以,asyncawait的组合 虽然相对于主程序,整体是异步的 但async内部的多个await 是同步顺序执行的。

3、错误处理也和程序的错误处理一致了

  • try catch组合代替了.catch(function(){ … })函数嵌套调用,可自动接住 promise函数中reject()抛出的异常信息。
  • 比如:
(async ()=>{ 
    try{ 
        var bang = await ming();
        bang = await hong(bang);
        tian(bang); 
    }catch(err){ 
        // 错误处理代码 
    } 
})()