JS 高级(七)ES6解构、class、promise

简介: JS 高级(七)ES6解构、class、promise

ES6: (ECMAScript第六个版本)

1. 解构(destruct)

       在旧 js 中,要想使用对象中的成员或数字中的元素,必须带着"对象名."或"数组名[ ]"前缀。但是在实际开发中,对象或数组的嵌套结构可能很深,这样的话前缀就可能写很长: "对象名.子对象名.子对象名....",非常麻烦。而解构方式就是用来来减少数组或对象的嵌套结构,便于使用。解构分为三种:


(1)数组解构


       数组解构用于从一个复杂的数组中只提取出需要的元素单独使用,格式如下:

 var [变量1, 变量2, ...] = 数组;

右边数组中相同下标位置的元素值会自动赋值给=左边相同下标位置的变量,一一对应;


变量1 = 数组[0];


变量2 = 数组[1];


要注意此处的 [ ] 并不带表创建一个新数组,仅仅是将个变量“装扮”成数组的样式。


举例:从数组中解构出年、月、日;


<script>
    var arr = [2021, 9, 3, 33];
    // 解构
    // 提取出数组中年月日三个值使用
    // var [a, b, c] = arr;
    // 如果不要年,只要月日
    var [, b, c] = arr;
    // console.log(`今年是${a}年`);
    console.log(`本月是${b}月`);
    console.log(`今天是${c}日`);
  </script>

(2)对象解构


       对象解构用于从一个大的对象中只提取出个别属性值单独使用,格式如下:


var { 属性名1:变量1, 属性名2:变量2,... } = 对象;

       当 : 左右两边的名字相同时,ES6为我们提供了简写方式:


       如果 : 左边的属性名刚好和 : 右边的变量名相同,则只需要写一个即可;此时一个名字就起到了两两个作用,既当属性名进行配对、又当变量名进行接值。简写后格式如下:


var {属性名1, 属性名2, ...} = 对象;

举例:解构出对象中的姓名和年龄单独使用;


<script>
    var lilei = {sname: "李雷",sage: 21}
    // 解构
    // var {sname: sname,sage: sage} = lilei;
    // 简写
    //一个名字两用:既当属性名配对;又当变量名接值
    var {sname,sage} = lilei
    console.log(`我叫${sname},今年${sage}岁`);
  </script>

(3)参数解构


       单靠参数默认值,无法解决任意一个形参不确定有没有的情况。只要实参值不确定没有,但是又要求实参值必须传给指定的形参,顺序不能乱,就要用用参数解构。格式如下:


//定义函数时: 
function 函数名({
  属性名1: 形参1, 
  属性名2: 形参2, 
  ... : ...
}){
  函数体
}
//调用函数时: 
函数名({
  属性名1: 实参值1, 
  属性名2: 实参值2, 
  ... : ...
})

简写后格式如下:


//定义函数时:
function 函数名({
  属性名1 = 默认值1, 
  属性名2 = 默认值2, 
  ... : ...
}){
  函数体
}
//调用时:
函数名({
  属性名1: 实参值1, 
  属性名2: 实参值2, 
  ... : ...
})

举例:定义订套餐函数,用户可任意更换套餐中菜品;


<script>
    // 定义一个点套餐的函数
    function order({
      zhushi = "香辣鸡腿堡",
      xiaochi = "烤鸡翅",
      yinliao = "可乐"
    }) {
      console.log(`
        您点的套餐为:
        主食:${zhushi}
        小吃:${xiaochi}
        饮料:${yinliao}
        `);
    }
    // a点默认套餐
    order({});
    // b自定
    order({
      zhushi: "牛肉汉堡",
      xiaochi: "鸡米花",
      yinliao: "雪碧"
    })
    // c只换主食
    order({
      zhushi: "烤全鸡"
    })
    // d只换小吃
    order({
      xiaochi: "正新鸡排"
    })
  </script>

打印结果如下:

image.png



2. class

       在旧 js 中,构造函数和原型对象是分开定义的,这样不符合"封装"概念;class 是程序中专门集中保存一种类型的所有子对象的统一属性结构和方法定义的程序结构。所以今后只要在 es6 中创建一种新的类型,包含构造函数 + 原型对象方法,都要用 class 来创建。


定义 class 的方法:


a. 先用 class{ } 包裹原构造函数+原型对象方法;(虽直接放在 class{} 内的方法定义,其实还是保存在原型对象中的)


b. 原构造函数名升级为整个 class 的名字,所有构造函数统一更名为 "constructor";


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


使用 class:


var 对象名=new class名(属性值,...);

       虽说用了 class,但本质并没有变:构造函数中的属性,依然会成为子对象的自有属性;直接定义在 class 中的方法,依然保存在子对象的原型对象中;子对象依然使用 _ _proto_ _ 指向原型对象。


举例:定义学生类型 class;


<script>
    // 定义学生类型,描述所有学生的统一结构和功能
    class Student {
      constructor(sname, sage) {
        this.sname = sname;
        this.sage = sage;
      }
      intr() {
        console.log(`我是${this.sname},我今年${this.sage}岁。`);
      }
    }
    // 创建一个学生对象
    var lilei = new Student("李雷", 21);
    console.log(lilei);
    lilei.intr();
  </script>

       但是上述用法也有一定的不足,若多个子对象共用相同的属性值,属性值应该放在哪里?


       虽然直接在 class 中定义的方法,都默认保存在原型对象中。但是直接在 class 中定义的属性,却不会成为共有属性,不会保存在原型对象中,而是成为每个子对象的自有属性。      


       在旧 js 中,是和共有方法一起放在原型对象中;而为了和其它主流开发语言尽量一致,ES6的 class 放弃了在原型对象中保存共有属性的方式。而是改为用静态属性 static 保存!


静态属性


       不需要创建子对象,单靠类型名就可直接访问的属性,就称为静态属性;今后在ES6中,如果希望所有子对象,都可使用一个共同的属性值时,都要用静态属性代替原来的原型对象属性。


       静态属性定义与调用格式如下:


//定义静态属性: 
class 类型名{
  static 共有属性名=属性值
    ... 
    ...
}
//访问静态属性: 
类型名.静态属性

注意访问静态属性时不可写成 this.静态属性 。


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


举例:使用静态属性替所有子对象保存共用的班级名;


<script>
    //定义学生类型,描述所有学生的统一结构和功能
    class Student {
      // 定义静态属性,可以多个对象共用
      static className = "初一2班";
      constructor(sname, sage) {
        this.sname = sname;
        this.sage = sage;
      }
      intr() {
        console.log(`我是${this.sname},我今年${this.sage}岁`);
      }
    }
    // 创建学生对象
    var lilei = new Student("李雷", 21);
    var hmm = new Student("韩梅梅", 20)
    console.log(lilei);
    console.log(hmm);
    lilei.intr();
    hmm.intr();
    // 一年后,初一升初二
    Student.className = "初二2班";
    lilei.intr();
    hmm.intr();
    console.log(Student); //log 默认输出的是Student构造函数的函数体(内容),不是对象结构
    console.dir(Student); //dir 不输出函数的内容,而是输出对象在内存中的存储结构
  </script>

两种类型间的继承


       两种 class 之间可能包含部分相同的属性结构和方法定义,这时候就应该用到继承。


进行继承的方法:


(1)额外创建一个父级 class;


       i. 父级 class 的构造函数中包含子类型 class 中相同部分的属性结构定义;


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


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


(2)让子类型 class 继承父类型的 class;


       i. 设置子类型的原型对象继承父类型的原型对象;

class 子类型 extends 父类型{ ... }

       ii. 使用 super 关键字,调用父级的父级类型的构造函数。


举例:使用类型间继承,实现飞机大战游戏中敌机和降落伞类型的定义,并创建敌机对象和降落伞对象;


<script>
    //定义爷爷class,保存共有属性结构和方法
    class enemy {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
      fly() {
        console.log(`目标飞到x=${this.x},y=${this.y}的位置。`);
      }
    }
    class Plane extends enemy {
      constructor(x, y, score) {
        super(x, y);
        this.score = score;
      }
      getScore() {
        console.log(`击落敌机得${this.score}分!`);
      }
    }
    class Jls extends enemy {
      constructor(x, y, ming) {
        super(x, y);
        this.ming = ming;
      }
      getMing() {
        console.log(`击落降落伞,得生命值${this.ming}!`);
      }
    }
    var p1 = new Plane(255, 255, 20);
    var p2 = new Jls(222, 125, 1)
    p1.fly();
    p1.getScore();
    p2.fly();
    p2.getMing();
  </script>

3. Promise

       promise 是专门保证多个异步任务必须顺序执行的一种特殊方式;在实际开发中,经常需要让多个异步任务顺序执行,而单纯先后调用多个异步函数的话,异步函数各自执行各自的,互不干扰,互相之间也不会等待,是错误的。


       解决以上问题可以用回调函数,举例:使用回调函数保证多个异步任务顺序执行;


<script>
    function zhangsan(box) {
      console.log(`张三起跑!`);
      setTimeout(function () {
        console.log(`张三跑到了终点!`);
        box();
      }, 6000)
    }
    function lisi(box) {
      console.log(`李四起跑!`);
      setTimeout(function () {
        console.log(`李四跑到了终点!`);
        box();
      }, 4000)
    }
    function wangwu() {
      console.log(`王五起跑!`);
      setTimeout(function () {
        console.log(`王五跑到了终点!`);
      }, 2000)
    }
    zhangsan(function () {
      lisi(function () {
        wangwu();
      });
    });
  </script>

但是用回调函数的话,如果要先后执行的任务多了,就会形成很深的嵌套结构——回调地狱,不仅极其不优雅,而且极其不便于维护。


       这种情况下,就需要用到 promise 来代替回调函数。步骤:

(1)定义前一项任务

<script>
    function zhangsan(box) {
      console.log(`张三起跑!`);
      setTimeout(function () {
        console.log(`张三跑到了终点!`);
        box();
      }, 6000)
    }
    function lisi(box) {
      console.log(`李四起跑!`);
      setTimeout(function () {
        console.log(`李四跑到了终点!`);
        box();
      }, 4000)
    }
    function wangwu() {
      console.log(`王五起跑!`);
      setTimeout(function () {
        console.log(`王五跑到了终点!`);
      }, 2000)
    }
    zhangsan(function () {
      lisi(function () {
        wangwu();
      });
    });
  </script>

(2)连接前后两个异步任务


前一项任务().then( 后一项任务 ) //注意最后一项任务不要再加()

两个任务之间也可以进行传参:


//前一项任务: 
function 前一项任务(){
   return new Promise(
   function(开关){
     var 变量=值
     调用开关( 变量 )
  }
 )
}
//后一项任务: 
function 后一项任务(形参){
   //形参=前一项任务中的变量值
}

举例:使用 Promise 模拟接力跑传接力棒;


<script>
    function zhangsan() {
      return new Promise(
        function (open) {
          var JieLiBang = "张三的接力棒";
          console.log(`张三拿着${JieLiBang}起跑!`);
          setTimeout(function () {
            console.log(`张三跑到了终点!`);
            open(JieLiBang);
          }, 6000)
        }
      )
    }
    function lisi(JieLiBang) {
      return new Promise(
        function (open) {
          console.log(`李四拿着${JieLiBang}起跑!`);
          setTimeout(function () {
            console.log(`李四跑到了终点!`);
            open(JieLiBang);
          }, 4000)
        }
      )
    }
    function wangwu(JieLiBang) {
      console.log(`王五拿着${JieLiBang}起跑!`);
      setTimeout(function () {
        console.log(`王五跑到了终点!`);
      }, 2000)
    }
    zhangsan().then(lisi).then(wangwu);
  </script>

(3)错误处理,格式如下:

//前一项任务: 
     function 前一项任务(){
   return new Promise(
        function(成功的开关, 失败的开关){
    var 变量=值
    原异步任务
          异步任务最后一句话
          如果异步任务执行成功
              调用成功的开关( 变量 )//此处开关通.then(),自动执行.then中的下一项任务
            否则如果一部任务执行失败
              调用失败的开关(错误提示信息)//此处开关通最后的.catch(),后续.then()不再执行。
        }
      )
     }
//调用时: 
    前一项任务()
     .then(下一项任务)
     .then(...)
     .catch(function(错误提示信息){ 错误处理代码 })

举例:假设有人在跑步过程中摔倒了,要添加错误处理;

<script>
    function zhangsan() {
      return new Promise(
        function (resolve, reject) {
          var JieLiBang = "张三的接力棒";
          console.log(`张三拿着${JieLiBang}起跑!`);
          setTimeout(function () {
            if (Math.random() < 0.5) {
              console.log(`张三拿着${JieLiBang}到达了终点!`);
              resolve(JieLiBang);
            } else {
              reject(`张三摔倒了!!`);
            }
          }, 6000)
        }
      )
    }
    function lisi(JieLiBang) {
      return new Promise(
        function (resolve, reject) {
          console.log(`李四拿着${JieLiBang}起跑!`);
          setTimeout(function () {
            if (Math.random() < 0.5) {
              console.log(`李四拿着${JieLiBang}跑到了终点!`);
              resolve(JieLiBang);
            } else {
              reject(`李四摔倒了!!`);
            }
          }, 4000)
        }
      )
    }
    function wangwu(JieLiBang) {
      console.log(`王五拿着${JieLiBang}起跑!`);
      setTimeout(function () {
        console.log(`王五跑到了终点!`);
      }, 2000)
    }
    zhangsan()
      .then(lisi)
      .then(wangwu)
      .catch(function (msg) {
        console.log(msg);
        console.log(`出现紧急状况,比赛终止!!!!`);
      });
  </script>

(4)Promise对象三大状态 (记忆)


a:当异步任务执行过程中,整个 new Promise() 对象处于 pending(挂起) 状态;


b:当异步任务成功执行完,调用成功的开关函数时,整个 new Promise() 对象切换为 fulfilled(成功) 状态,new Promise() 会自动调用 .then() 执行下一项任务;


c:当异步任务执行出错,调用失败的开关函数,整个 new Promise() 对象切换为 rejected(出错) 状态,new Promise() 会自动调用 .catch() 执行错误处理代码。


在行业中,这两个开关常用(规范):


       正确的开关:resolve(同意继续)


       失败的开关:reject(拒绝继续)


相关文章
|
4天前
|
存储 JavaScript 索引
js开发:请解释什么是ES6的Map和Set,以及它们与普通对象和数组的区别。
ES6引入了Map和Set数据结构。Map的键可以是任意类型且有序,与对象的字符串或符号键不同;Set存储唯一值,无重复。两者皆可迭代,支持for...of循环。Map有get、set、has、delete等方法,Set有add、delete、has方法。示例展示了Map和Set的基本操作。
17 3
|
1月前
|
缓存 JavaScript 数据安全/隐私保护
js开发:请解释什么是ES6的Proxy,以及它的用途。
`ES6`的`Proxy`对象用于创建一个代理,能拦截并自定义目标对象的访问和操作,应用于数据绑定、访问控制、函数调用的拦截与修改以及异步操作处理。
18 3
|
1月前
|
JavaScript
js开发:请解释什么是ES6的类(class),并说明它与传统构造函数的区别。
ES6的类提供了一种更简洁的面向对象编程方式,对比传统的构造函数,具有更好的可读性和可维护性。类使用`class`定义,`constructor`定义构造方法,`extends`实现继承,并可直接定义静态方法。示例展示了如何创建`Person`类、`Student`子类以及它们的方法调用。
22 2
|
4天前
|
JavaScript 前端开发
js开发:请解释什么是ES6的Generator函数,以及它的用途。
ES6的Generator函数是暂停/恢复功能的特殊函数,利用yield返回多个值,适用于异步编程和流处理,解决了回调地狱问题。例如,一个简单的Generator函数可以这样表示: ```javascript function* generator() { yield &#39;Hello&#39;; yield &#39;World&#39;; } ``` 创建实例后,通过`.next()`逐次输出&quot;Hello&quot;和&quot;World&quot;,展示其暂停和恢复的特性。
15 0
|
1月前
|
JavaScript 前端开发
js开发:请解释什么是ES6的async/await,以及它如何解决回调地狱问题。
ES6的`async/await`是基于Promise的异步编程工具,能以同步风格编写异步代码,提高代码可读性。它缓解了回调地狱问题,通过将异步操作封装为Promise,避免回调嵌套。错误处理更直观,类似同步的try...catch。
|
1月前
|
JavaScript
js开发:请解释什么是ES6的Symbol,以及它的用途。
ES6的Symbol数据类型创建唯一值,常用于对象属性键(防冲突)和私有属性。示例展示了如何创建及使用Symbol:即使描述相同,两个Symbol也不等;作为对象属性如`obj[symbol1] = &#39;value1&#39;`;也可作枚举值,如`Color.RED = Symbol(&#39;red&#39;)`。
|
1月前
|
JavaScript
js开发:请解释什么是ES6的扩展运算符(spread operator),并给出一个示例。
ES6的扩展运算符(...)用于可迭代对象展开,如数组和对象。在数组中,它能将一个数组的元素合并到另一个数组。例如:`[1, 2, 3, 4, 5]`。在对象中,它用于复制并合并属性,如`{a: 1, b: 2, c: 3}`。
12 3
|
1月前
|
JavaScript
js开发:请解释什么是ES6的默认参数(default parameters),并给出一个示例。
ES6允许在函数参数中设置默认值,如`function greet(name = &#39;World&#39;) {...}`。当调用函数不传入`name`参数时,它将默认为&#39;World&#39;,提升代码简洁性和可读性。例如:`greet()`输出&quot;Hello, World!&quot;,`greet(&#39;Alice&#39;)`输出&quot;Hello, Alice!&quot;。
16 4
|
1月前
|
JavaScript 前端开发
js开发:请解释什么是ES6的解构赋值(destructuring assignment),并给出一个示例。
ES6的解构赋值简化了JavaScript中从数组和对象提取数据的过程。例如,`[a, b, c] = [1, 2, 3]`将数组元素赋值给变量,`{name, age} = {name: &#39;张三&#39;, age: 18}`则将对象属性赋值给对应变量,提高了代码的可读性和效率。
|
2月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
27 0