ECMAScript

1、ECMASript 6新特性

1.1 let关键字

  • let关键字用来声明变量,使用let声明的变量有几个特点:

    1
    2
    3
    4
    let a; // 单个声明
    let b,c,d; // 批量声明
    let e = 100; // 单个声明并赋值
    let f = 521, g = 'iloveyou', h = [] // 批量声明并赋值
    • 不允许重复声明。

      1
      2
      let a
      let a = 12 // a已经声明,此处为错误用法!
    • 块儿级作用域(局部变量)。

      1
      2
      3
      4
      5
      // if else while for 
      {
      let fruit='apple'
      }
      console.log(fruit) // error
    • 不存在变量提升。

      1
      2
      3
      4
      5
      6
      // 什么是变量提升:就是在变量创建之前使用
      //(比如输出:输出的是默认值),let不存在,var存在
      console.log(people1) // 可输出默认值
      console.log(people2) // 报错:Uncaught ReferenceError: Cannot access 'people2' before initialization
      var people1 = "Maria" // 存在变量提升
      let people2 = "Jim" // 不存在变量提升
    • 不影响作用域链。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 什么是作用域链:
      // 很简单,就是代码块内有代码块,跟常规编程语言一样,
      // 上级代码块中的局部变量下级可用
      {
      let p = "Maria"
      function fn(){
      console.log(p) // 这里是可以使用的
      }
      fn()
      }
  • 实际案例:点击div更换背景颜色。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>点击 DIV 换色</title>
    <link crossorigin="anonymous" href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
    rel="stylesheet">
    <style>
    .item {
    width: 100px;
    height: 50px;
    border: solid 1px rgb(42, 156, 156);
    float: left;
    margin-right: 10px;
    }
    </style>
    </head>

    <body>
    <div class="container">
    <h2 class="page-header">点击切换颜色</h2>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    </div>
    <script>
    // 获取div元素对象
    let items = document.getElementsByClassName('item');

    // 遍历并绑定事件
    for(let i = 0;i<items.length;i++){
    items[i].onclick = function(){
    // 修改当前元素的背景颜色
    // this.style.background = 'pink';
    items[i].style.background = 'pink';
    }
    }
    // 当var=3的时候,点击事件开始向外层作用域找,找不到,就是windows.i,此时是3。如果是let i,具有块级作用域,所以每一次触碰事件的i都是不同的。
    </script>
    </body>

    </html>
  • 应用场景:以后声明变量使用let就对了。

1.2 const关键字

  • const关键字用来声明常量,const声明有以下特点:

    const关键字用来声明常量,const声明有以下特点:(常量的含义是指向的对象不能修改,但是可以改变对象内部的属性)。

    • 声明必须赋初始值。

      1
      2
      3
      // const声明常量,一经声明,则不允许修改
      const fruit1 // 未赋值,报错Uncaught SyntaxError: Missing initializer in const declaration
      const fruit2 = "apple"
    • 标识符一般为大写(习惯)。

      1
      2
      const FRUIT = "apple" 
      console.log(FRUIT) // "apple"
    • 不允许重复声明。

      1
      2
      const FRUIT = "apple" 
      const FRUIT = "apple" // 报错,不可重复声明
    • 值不允许修改。

      1
      2
      3
      // 当我们修饰的标识符不会被再次赋值时,就可以使用const来保证数据的安全性。
      const FRUIT = "apple"
      FRUIT = "banana" // 错误
    • 块儿级作用域(局部)。

      1
      2
      3
      4
      5
      6
      {
      const FRUIT = "apple"
      console.log(FRUIT) // "apple"
      }

      console.log(FRUIT) // 错误,FRUIT未定义
    • 对于数组和对象的元素修改,不算做对常量的修改,不会报错。

      1
      2
      3
      // 对数组元素的修改和对对象内部的修改是可以的(数组和对象存的是引用地址)
      const FRUIT = ['apple', 'banana', 'peach', 'orange'];
      FRUIT.push('watermalen'); // 不报错,常量地址没有发生变化
  • 应用场景:声明对象类型使用const,非对象类型声明选择let。

1.3 变量的解构赋值

  • ES6允许按照一定模式从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。

    • 数组的解构。

      1
      2
      3
      4
      5
      const BOOKS = ['三体','海子的诗','西游记']
      let [san,hai,xi] = BOOKS
      console.log(san) // '三体'
      console.log(hai) // '孩子的诗'
      console.log(xi) // '西游记'
    • 对象的解构。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      const ZHANGSAN = {
      name: '张三',
      age: 23,
      gender: '男',
      speak: function() {
      console.log("Hello,I'm ZhangSan!")
      }
      }

      let {name,age,gender,speak} = ZHANGSAN
      console.log(name) // '张三'
      console.log(age) // 23
      console.log(gender) // '男'
      console.log(speak) // function(){...}
      speak() // "Hello,I'm ZhangSan!"

      let {speak} = ZHANGSAN;
      speak();
  • 注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式。

1.4 模板字符串

  • 模板字符串(template string)是增强版的字符串,用反引号(`)标识,特点:

    • 字符串中可以出现换行符。

      1
      2
      3
      4
      5
      6
      let str = `<ul>
      <li>沈腾</li>
      <li>玛丽</li>
      <li>魏翔</li>
      <li>艾伦</li>
      </ul>`;
    • 变量拼接(替换/插入)。

      1
      2
      3
      let lovest = '魏翔';
      let out = `${lovest}是我心目中最搞笑的演员!!`;
      console.log(out);
  • 注意:当遇到字符串与变量拼接的情况使用模板字符串。

1.5 简化对象写法

  • ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let name = 'love';
    let change = function(){
    console.log('我们可以改变你!!');
    }

    const school = {
    name,
    change,
    improve(){
    console.log("我们可以提高你的技能");
    }
    }

    console.log(school);
  • 注意:对象简写形式简化了代码,所以以后用简写就对了。

1.6 箭头函数

  • ES6允许使用箭头=>定义函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let fn = function(){

    }

    let fn = (a,b) => {
    return a + b;
    }
    // 调用函数
    let result = fn(1, 2);
    console.log(result);
    • this是静态的,this始终指向函数声明时所在作用域下的this的值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      function getName1(){
      console.log(this.name)
      }

      let getName2 = () => {
      console.log(this.name)
      }

      window.name = '蛙哈哈'
      const school = {
      name: 'wahaha'
      }

      // 直接调用
      getName1() // 蛙哈哈
      getName2() // 蛙哈哈

      // call
      getName1.call(school) // wahaha
      getName2.call(school) // 蛙哈哈
    • 不能作为构造实例化对象。

      1
      2
      3
      4
      5
      6
      let Person = (name, age) => {
      this.name = name;
      this.age = age;
      }
      let me = new Person('xiao',30);
      console.log(me); // 错误
    • 不能使用arguments 变量。

      1
      2
      3
      4
      let fn = () => {
      console.log(arguments) // 错误
      }
      fn(1,2,3)
    • 箭头函数的简写。

      • 省略小括号,当形参有且只有一个的时候可以省略小括号。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        // let add = (n) => {
        // console.log(n + n)
        // }
        // add(3) // 6

        // 简写:
        let add = n => {
        console.log(n + n)
        }
        add(3) // 6
      • 省略花括号{ },仅当函数语句只有一条语句时。此时,'return' 也需要省略,结果即是返回值

        1
        2
        let pow = n => n * n
        console.log(pow(8)) // 64
  • 箭头函数实践:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>箭头函数实践</title>
    <style>
    div {
    width: 200px;
    height: 200px;
    background: #58a;
    }
    </style>
    </head>
    <body>
    <div id="ad"></div>
    <script>
    // 需求-1 点击 div 2s 后颜色变成『粉色』
    // 获取元素
    let ad = document.getElementById('ad');
    // 绑定事件
    ad.addEventListener("click", function(){
    // 保存 this 的值
    // let _this = this;
    // 定时器
    setTimeout(() => {
    // 修改背景颜色 this
    // console.log(this);
    // _this.style.background = 'pink';
    this.style.background = 'pink';
    }, 2000);
    });

    //需求-2 从数组中返回偶数的元素
    const arr = [1,6,9,10,100,25];
    // const result = arr.filter(function(item){
    // if(item % 2 === 0){
    // return true;
    // }else{
    // return false;
    // }
    // });

    const result = arr.filter(item => item % 2 === 0);

    console.log(result);

    // 箭头函数适合与 this 无关的回调. 定时器, 数组的方法回调
    // 箭头函数不适合与 this 有关的回调. 事件回调, 对象的方法

    </script>
    </body>

    </html>

1.7 函数参数默认值

  • ES6允许给函数参数赋值初始值:

    • 可以给形参赋初始值,一般位置要靠后(潜规则)。

      1
      2
      3
      4
      5
      6
      function add(a,b,c=12){
      return a+b+c;
      }
      let result = add (1,2)
      console.log(result) // 15
      // 如果上面代码没有给形参c赋初始值,则执行add (1,2)时,形参c没有对应的参数,默认为NaN,所以add (1,2)的执行结果为NaN
    • 与解构赋值结合。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      function ap({host='127.0.0.1', username, password, port}){
      console.log(host,username,password,port) //
      }
      ap({
      host: 'localhost',
      username:'admin',
      password:'000000',
      port:3000
      })
      // 执行结果:localhost admin 000000 3000

1.8 rest参数

  • ES6引入rest参数,用于获取函数的实参,用来代替arguments:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // ES5 获取实参的方式
    function date(){
    console.log(arguments);
    }
    date('白芷','阿娇','思慧');

    function date(...args){
    console.log(args); // filter some every map
    }
    date('阿娇','柏芝','思慧');

    // rest 参数必须要放到参数最后
    function fn(a,b,...args){
    console.log(a);
    console.log(b);
    console.log(args);
    }
    fn(1,2,3,4,5,6);
  • 注意:rest参数非常适合不定个数参数函数的场景。

1.9 spread扩展运算符

  • 扩展运算符(spread)也是三个点(…)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。

    1
    2
    3
    4
    5
    6
    const tfboys=['易烊千玺','王源','王俊凯']
    function show(){
    console.log(arguments)
    }
    show(tfboys) // 一个参数,数组:['易烊千玺', '王源', '王俊凯']
    show(...tfboys) // 相当于show('易烊千玺','王源','王俊凯')
  • 扩展运算符应用:

    • ①数组的合并。

      1
      2
      3
      4
      5
      const arr1 = ['aa','bb'] 
      const arr2 = ['cc','dd']
      // const arr = arr1.concat(arr2) // ['aa', 'bb', 'cc', 'dd']
      const arr = [...arr1, ...arr2]
      console.log(arr) // ['aa', 'bb', 'cc', 'dd']
    • ②数组的克隆。

      1
      2
      3
      4
      // 如果数组里面有引用类型的数据,则整个为浅拷贝;否则,就是完全拷贝。
      const arr1 = ['a','b','c']
      const arr2 = [...arr1]
      console.log(arr2) // ['a', 'b', 'c']
    • ③将伪数组转换为真正的数组。

      1
      2
      3
      const divs = documents.querySelectorAll('div') 
      const divArr = [...divs]
      console.log(divArr) // [div,div,div]

1.10 Symbol

  • ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,是一种类似于字符串的数据类型。

    • Symbol的值是唯一的,用来解决命名冲突的问题。
    • Symbol值不能与其他数据进行运算。
    • Symbol定义的对象属性不能使用for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名。
  • symbol和其他几种数据类型不同,symbol没有字面量的写法也不可以使用new关键字创建。具体声明方法如下。

    1
    2
    var s1 = Symbol('id1');
    console.log(typeof s1); // symbol

    一个symbol类型的变量使用typeof 返回值是字符串symbol,并且symbol不能与其他数据类型的值进行计算。

  • Symbol的参数一般会存放一个字符串用来标识不同的值,如果Symbol的参数是一个对象,那么就会调用该对象的toString方法,将其转换成字符串,然后在生成一个Symbol值。

    1
    2
    3
    4
    5
    var s1 = Symbol('id1');
    var s2 = Symbol('id1');
    console.log(s1 == s2);// false
    var s3 = Symbol({});
    console.log(s3);//Symbol([object Object])

    Symbol里面的参数是表示当前这个Symbol值的描述,即便参数相同这两个Symbol值也不会相同。

  • Symbol作为对象属性名时不能用运算符,要用方括号。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const sy = Symbol(666);
    var obj = {
    a: 12,
    b: 3,
    [sy]: 3
    }
    for(var key in obj) {
    console.log(key);// a b
    }
    console.log(Object.keys(obj));//返回array对象,其中只有a,b两个值

    注意点:Symbol属性不能被枚举,也就是说不能通过for…in、Object.keys() 、Object.getOwnPropertyNames()方法返回。

  • 如果要读取到一个对象的Symbol属性,可以通过Object.getOwnPropertySymbols()取到。Object.getOwnPropertySymbols可以获取符号的属性名,返回的是一个数组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const sy = Symbol(666);
    var obj = {
    a: 12,
    b: 3,
    [sy]: 3
    }
    const syb = Object.getOwnPropertySymbols(obj);
    console.log(syb); // 返回array对象,其中存放一个值Symbol(666)
    console.log(syb[0] == sy); // true
  • Symbol里面提供了一个for方法,这个方法是以一个字符串作为参数,首先会在全局搜索被注册的Symbol中是否有该字符串参数作为名称的Symbol值,如果有即返回该Symbol值,若没有则新建并返回一个以该字符串参数为名称的Symbol值,并注册在全局环境中供搜索。

    1
    2
    3
    4
    5
    6
    let yellow = Symbol("Yellow");
    let yellow1 = Symbol.for("Yellow");
    console.log(yellow === yellow1) // false

    let yellow2 = Symbol.for("Yellow");
    console.log(yellow1 === yellow2) // true
  • Symbol.keyFor方法是返回一个已登记的Symbol标识字符串,如果有登记,就返回对应的字符串,没有登记,就返回undefined。

    1
    2
    3
    4
    var s = Symbol.for("f");
    console.log(Symbol.keyFor(s)); // f
    var s3 = Symbol("foo");
    console.log(Symbol.keyFor(s3)); // undefined
  • symbol应用:

    • 给对象添加方法方式一:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      let game = {
      name:'俄罗斯方块',
      up: function(){},
      down: function(){}
      };

      let methods = {
      up:Symbol(),
      down:Symbol()
      }
      game[methods.up]=function(){
      console.log('跳起来了!')
      }
      game[methods.down]=function(){
      console.log('蹲下去了!')
      }
      console.log(game)
    • 给对象添加方法方式二:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      let youxi = {
      name:"狼人杀",
      [Symbol('say')]: function(){
      console.log("我可以发言")
      },
      [Symbol('zibao')]: function(){
      console.log('我可以自爆');
      }
      }

      console.log(youxi)
  • Symbol内置值:除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。

    内置值 说明
    Symbol.hasInstance 当其他对象使用 instanceof 运算符,判断是否为该对 象的实例时,会调用这个方法
    Symbol.isConcatSpreadable 对象的 Symbol.isConcatSpreadable 属性等于的是一个 布尔值,表示该对象用于 Array.prototype.concat()时, 是否可以展开。
    Symbol.species 创建衍生对象时,会使用该属性
    Symbol.match 当执行 str.match(myObject) 时,如果该属性存在,会 调用它,返回该方法的返回值。
    Symbol.replace 当该对象被 str.replace(myObject)方法调用时,会返回 该方法的返回值。
    Symbol.search 当该对象被 str.search (myObject)方法调用时,会返回 该方法的返回值。
    Symbol.split 当该对象被 str.split(myObject)方法调用时,会返回该 方法的返回值。
    Symbol.iterator 对象进行 for…of 循环时,会调用 Symbol.iterator 方法, 返回该对象的默认遍历器
    Symbol.toPrimitive 该对象被转为原始类型的值时,会调用这个方法,返 回该对象对应的原始类型值。
    Symbol. toStringTag 在该对象上面调用 toString 方法时,返回该方法的返 回值
    Symbol. unscopables 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Person{
    static [Symbol.hasInstance](param){
    console.log(param);
    console.log("我被用来检测类型了");
    return false;
    }
    }

    let o = {"a":"123"};

    console.log(o instanceof Person);

    const arr = [1,2,3];
    const arr2 = [4,5,6];
    arr2[Symbol.isConcatSpreadable] = false;
    console.log(arr.concat(arr2));

1.11 迭代器

  • 迭代器(lterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署lterator接口,就可以完成遍历操作。

    • ES6创造了一种新的遍历命令for…of循环,lterator接口主要供for…of消费。
    • 原生具备iterator接口的数据(可用for of遍历)。
      • Array。
      • Arguments。
      • Set。
      • Map。
      • String。
      • TypedArray。
      • NodeList。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let arr = ['a','b','c','d']

    for(let n of arr) {
    console.log(n) // a b c d
    }

    for(let n in arr) {
    console.log(n) // 0 1 2 3
    }
  • 工作原理:

    • 创建一个指针对象,指向当前数据结构的起始位置。
    • 第一次调用对象的next方法,指针自动指向数据结构的第一个成员。
    • 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员。
    • 每调用next方法返回一个包含value和done属性的对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const array = ['AA','BB','CC','DD'] 
    // for(let v of array){
    // console.log(v) // 'AA','BB','CC','DD'
    // for in保存的是键名,for of保存的是键值
    // }

    let iterator = array[Symbol.iterator]()

    console.log(iterator.next()) // {{value:'AA',done:false}}
    console.log(iterator.next()) // {{value:'BB',done:false}}
    console.log(iterator.next()) // {{value:'CC',done:false}}
    console.log(iterator.next()) // {{value:'DD',done:false}}
    console.log(iterator.next()) // {{value:undefined,done:true}}
  • 注意:需要自定义遍历数据的时候,要想到迭代器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自定义遍历数据</title>
    </head>

    <body>
    <script>
    //声明一个对象
    const banji = {
    name: "终极一班",
    stus: [
    'xiaoming',
    'xiaoning',
    'xiaotian',
    'knight'
    ],
    [Symbol.iterator]() {
    // 索引变量
    let index = 0;
    let _this = this;
    return {
    next: function () {
    if (index < _this.stus.length) {
    const result = { value: _this.stus[index], done: false };
    // 下标自增
    index++;
    // 返回结果
    return result;
    }else{
    return {value: undefined, done: true};
    }
    }
    };
    }
    }

    //遍历这个对象
    for (let v of banji) {
    console.log(v);
    }
    </script>
    </body>
    </html>

1.12 生成器

  • 生成器函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。

    • 一个generator看上去像一个函数,但可以返回多次
    • generator和函数不同的是,generator由function * 定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 生成器其实就是一个特殊的函数
    // yield为函数代码的分隔符
    function * gen(){
    console.log(111);
    yield '一只没有耳朵';
    console.log(222);
    yield '一只没有尾部';
    console.log(333);
    yield '真奇怪';
    console.log(444);
    }

    let iterator = gen();
    iterator.next(); // 会执行第一段代码并将yield的值返回
    iterator.next();
    iterator.next();
    iterator.next();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function * gen(){
    yield '一只没有耳朵';
    yield '一只没有尾部';
    yield '真奇怪';
    }

    //遍历
    for(let v of gen()){
    console.log(v);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function * gen(){
    yield '一只没有耳朵';
    yield '一只没有尾部';
    yield '真奇怪';
    }

    let iterator = gen();
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
  • 生成器函数的参数传递:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function * gen(args){
    console.log(args)
    let one = yield 111
    console.log(one)
    let two = yield 222
    console.log(two)
    let three = yield 333
    console.log(three)
    }

    let iterator = gen('AAA')
    console.log(iterator.next())
    console.log(iterator.next('BBB')) // next中传入的BBB将作为yield 111的返回结果
    console.log(iterator.next('CCC')) // next中传入的CCC将作为yield 222的返回结果
    console.log(iterator.next('DDD')) // next中传入的DDD将作为yield 333的返回结果
  • 生成器函数实例:

    • 用生成器函数的方式解决回调地狱问题。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      <!DOCTYPE html>
      <html lang="en">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>生成器函数实例</title>
      </head>

      <body>
      <script>
      // 异步编程 文件操作 网络操作(ajax, request) 数据库操作
      // 1s 后控制台输出 111 2s后输出 222 3s后输出 333
      // 回调地狱
      // setTimeout(() => {
      // console.log(111);
      // setTimeout(() => {
      // console.log(222);
      // setTimeout(() => {
      // console.log(333);
      // }, 3000);
      // }, 2000);
      // }, 1000);

      function one(){
      setTimeout(()=>{
      console.log(111);
      iterator.next();
      },1000)
      }

      function two(){
      setTimeout(()=>{
      console.log(222);
      iterator.next();
      },2000)
      }

      function three(){
      setTimeout(()=>{
      console.log(333);
      iterator.next();
      },3000)
      }

      function * gen(){
      yield one();
      yield two();
      yield three();
      }

      //调用生成器函数
      let iterator = gen();
      iterator.next();

      </script>
      </body>

      </html>
    • 模拟异步获取数据。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>生成器函数</title>
      </head>
      <body>
      <script>
      //模拟获取 用户数据 订单数据 商品数据
      function getUsers(){
      setTimeout(()=>{
      let data = '用户数据';
      //调用 next 方法, 并且将数据传入
      iterator.next(data);
      }, 1000);
      }

      function getOrders(){
      setTimeout(()=>{
      let data = '订单数据';
      iterator.next(data);
      }, 1000)
      }

      function getGoods(){
      setTimeout(()=>{
      let data = '商品数据';
      iterator.next(data);
      }, 1000)
      }

      function * gen(){
      let users = yield getUsers();
      let orders = yield getOrders();
      let goods = yield getGoods();
      }

      //调用生成器函数
      let iterator = gen();
      iterator.next();

      </script>
      </body>
      </html>

1.13 Promise

  • Promise是ES6引入的异步编程的新解决方案。语法上Promise是一个构造函数,用来封装异步操作并可以获取其成功失败的结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const p = new Promise((resolve, reject) => {
    setTimeout(()=>{
    let data='数据库数据'
    // resolve(data)
    reject(data)
    })
    })

    p.then(function(value){ // 成功则执行第一个回调函数
    console.log(value)
    },function(reason){ // 失败则执行第二个
    console.error(reason)
    })
  • Promise.then()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //创建 promise 对象
    const p = new Promise((resolve, reject)=>{
    setTimeout(()=>{
    resolve('用户数据');
    // reject('出错啦');
    }, 1000)
    });

    //调用 then 方法 then方法的返回结果是 Promise 对象, 对象状态由回调函数的执行结果决定
    //1. 如果回调函数中返回的结果是 非 promise 类型的属性, 状态为成功, 返回值为对象的成功的值
    const result = p.then(value => {
    console.log(value);
    //1. 非 promise 类型的属性
    // return 'iloveyou';
    //2. 是 promise 对象
    // return new Promise((resolve, reject)=>{
    // // resolve('ok');
    // reject('error');
    // });
    //3. 抛出错误
    // throw new Error('出错啦!');
    throw '出错啦!';
    }, reason=>{
    console.warn(reason);
    });

    console.log(result)

    //链式调用
    p.then(value=>{

    }).then(value=>{

    });
  • Promise.catch()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const p = new Promise((resolve, reject)=>{
    setTimeout(()=>{
    //设置 p 对象的状态为失败, 并设置失败的值
    reject("出错啦!");
    }, 1000)
    });

    // p.then(function(value){}, function(reason){
    // console.error(reason);
    // });

    p.catch(function(reason){
    console.warn(reason);
    });
  • promise应用:

    • promise封装读取文件。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      //1. 引入 fs 模块
      const fs = require('fs');

      //2. 调用方法读取文件
      // fs.readFile('./resources/为学.md', (err, data)=>{
      // //如果失败, 则抛出错误
      // if(err) throw err;
      // //如果没有出错, 则输出内容
      // console.log(data.toString());
      // });

      //3. 使用 Promise 封装
      const p = new Promise(function(resolve, reject){
      fs.readFile("./resources/为学.mda", (err, data)=>{
      //判断如果失败
      if(err) reject(err);
      //如果成功
      resolve(data);
      });
      });

      p.then(function(value){
      console.log(value.toString());
      }, function(reason){
      console.log("读取失败!!");
      });
    • promise封装ajax。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      // 接口地址: https://api.apiopen.top/getJoke
      const p = new Promise((resolve, reject) => {
      //1. 创建对象
      const xhr = new XMLHttpRequest();

      //2. 初始化
      xhr.open("GET", "https://api.apiopen.top/getJ");

      //3. 发送
      xhr.send();

      //4. 绑定事件, 处理响应结果
      xhr.onreadystatechange = function () {
      // 判断
      if (xhr.readyState === 4) {
      // 判断响应状态码 200-299
      if (xhr.status >= 200 && xhr.status < 300) {
      // 表示成功
      resolve(xhr.response);
      } else {
      // 如果失败
      reject(xhr.status);
      }
      }
      }
      })

      // 指定回调
      p.then(function(value){
      console.log(value);
      }, function(reason){
      console.error(reason);
      });
    • promise读取多个文件。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      //引入 fs 模块
      const fs = require("fs");

      // fs.readFile('./resources/为学.md', (err, data1)=>{
      // fs.readFile('./resources/插秧诗.md', (err, data2)=>{
      // fs.readFile('./resources/观书有感.md', (err, data3)=>{
      // let result = data1 + '\r\n' +data2 +'\r\n'+ data3;
      // console.log(result);
      // });
      // });
      // });

      //使用 promise 实现
      const p = new Promise((resolve, reject) => {
      fs.readFile("./resources/为学.md", (err, data) => {
      resolve(data);
      });
      });

      p.then(value => {
      return new Promise((resolve, reject) => {
      fs.readFile("./resources/插秧诗.md", (err, data) => {
      resolve([value, data]);
      });
      });
      }).then(value => {
      return new Promise((resolve, reject) => {
      fs.readFile("./resources/观书有感.md", (err, data) => {
      //压入
      value.push(data);
      resolve(value);
      });
      })
      }).then(value => {
      console.log(value.join('\r\n'));
      });

1.14 Set

  • ES6提供了新的数据结构Set(集合)。它类似于数组,但成员的值都是唯 一的,集合实现了iterator接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:

    • .size返回集合的元素个数。
    • .add()增加一个新元素,返回当前集合。
    • .delete()删除元素,返回boolean值。
    • .has()检测集合中是否包含某个元素,返回boolean值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //声明一个 set
    let s = new Set();
    let s2 = new Set(['大事儿','小事儿','好事儿','坏事儿','小事儿']);

    //元素个数
    // console.log(s2.size);
    //添加新的元素
    // s2.add('喜事儿');
    //删除元素
    // s2.delete('坏事儿');
    //检测
    // console.log(s2.has('糟心事'));
    //清空
    // s2.clear();
    // console.log(s2);

    for(let v of s2){
    console.log(v);
    }
  • Set实践:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    let arr = [1,2,3,4,5,4,3,2,1];
    //1. 数组去重
    let result = [...new Set(arr)];
    console.log(result);
    //2. 交集
    let arr2 = [4,5,6,5,6];
    let result = [...new Set(arr)].filter(item => {
    let s2 = new Set(arr2);// 4 5 6
    if(s2.has(item)){
    return true;
    }else{
    return false;
    }
    });
    let result = [...new Set(arr)].filter(item => new Set(arr2).has(item));
    console.log(result);

    //3. 并集
    let union = [...new Set([...arr, ...arr2])];
    console.log(union);

    //4. 差集
    let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
    console.log(diff);

1.15 Map

  • ES6提供了Map数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了iterator接口,所以可以使用『扩展运算符』和「for…of…』进行遍历。

    • .size 获取Map的键值对数量(最外层)。
    • .set(key,value) 添加键值对。
    • .delete(key) 删除键为key的键值对。
    • .get(key) 获取键为key的值。
    • for...of 遍历里面的每一个键值对。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    //声明 Map
    let m = new Map();

    //添加元素
    m.set('name','尚硅谷');
    m.set('change', function(){
    console.log("我们可以改变你!!");
    });
    let key = {
    school : 'ATGUIGU'
    };
    m.set(key, ['北京','上海','深圳']);

    //size
    // console.log(m.size);

    //删除
    // m.delete('name');

    //获取
    // console.log(m.get('change'));
    // console.log(m.get(key));

    //清空
    // m.clear();

    //遍历
    for(let v of m){
    console.log(v);
    }

    // console.log(m);

1.16 class类

  • ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

    • ES5使用构造函数实例化对象:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      //手机
      function Phone(brand, price){
      this.brand = brand;
      this.price = price;
      }

      //添加方法
      Phone.prototype.call = function(){
      console.log("我可以打电话!!");
      }

      //实例化对象
      let Huawei = new Phone('华为', 5999);
      Huawei.call();
      console.log(Huawei);
    • ES6的class语法实现。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      //class
      class Shouji{
      //构造方法 名字不能修改
      constructor(brand, price){
      this.brand = brand;
      this.price = price;
      }

      //方法必须使用该语法, 不能使用 ES5 的对象完整形式
      call(){
      console.log("我可以打电话!!");
      }
      }

      let onePlus = new Shouji("1+", 1999);

      console.log(onePlus);

静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数对象和实例对象的属性无法互通,而实例对象的属性和构造函数的原型对象相通
function Phone(){

}
Phone.name = '手机';
Phone.change = function(){
console.log("我可以改变世界");
}
Phone.prototype.size = '5.5inch';

let nokia = new Phone();

console.log(nokia.name); // undefined
// nokia.change(); // 报错
console.log(nokia.size);
1
2
3
4
5
6
7
8
9
10
11
class Phone{
//静态属性
static name = '手机';
static change(){
console.log("我可以改变世界");
}
}

let nokia = new Phone();
console.log(nokia.name); // undefined
console.log(Phone.name); // 手机

构造函数继承

  • ES5实现构造函数继承:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    //手机
    function Phone(brand, price){
    this.brand = brand;
    this.price = price;
    }

    Phone.prototype.call = function(){
    console.log("我可以打电话");
    }

    //智能手机
    function SmartPhone(brand, price, color, size){
    Phone.call(this, brand, price);
    this.color = color;
    this.size = size;
    }

    //设置子级构造函数的原型
    SmartPhone.prototype = new Phone;
    SmartPhone.prototype.constructor = SmartPhone;

    //声明子类的方法
    SmartPhone.prototype.photo = function(){
    console.log("我可以拍照")
    }

    SmartPhone.prototype.playGame = function(){
    console.log("我可以玩游戏");
    }

    const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');

    console.log(chuizi);
  • ES6实现构造函数继承:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class Phone{
    //构造方法
    constructor(brand, price){
    this.brand = brand;
    this.price = price;
    }
    //父类的成员属性
    call(){
    console.log("我可以打电话!!");
    }
    }

    class SmartPhone extends Phone {
    //构造方法
    constructor(brand, price, color, size){
    super(brand, price);// Phone.call(this, brand, price)
    this.color = color;
    this.size = size;
    }

    photo(){
    console.log("拍照");
    }

    playGame(){
    console.log("玩游戏");
    }

    // 重写!
    call(){
    console.log('我可以进行视频通话');
    }
    }

    const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
    // console.log(xiaomi);
    xiaomi.call();
    xiaomi.photo();
    xiaomi.playGame();

get和set设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Phone{
get price(){
console.log("价格属性被读取了");
return 'iloveyou';
}

set price(newVal){
console.log('价格属性被修改了');
}
}

//实例化对象
let s = new Phone();

console.log(s.price);
s.price = 'free';

1.17 数值扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//0. Number.EPSILON 是 JavaScript 表示的最小精度
//EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16
function equal(a, b){
if(Math.abs(a-b) < Number.EPSILON){
return true;
}else{
return false;
}
}
console.log(0.1 + 0.2 === 0.3); // false
console.log(equal(0.1 + 0.2, 0.3)) // true

//1. 二进制和八进制
let b = 0b1010;
let o = 0o777;
let d = 100;
let x = 0xff;
console.log(b); // 10
console.log(o); // 511
console.log(d); // 100
console.log(x); // 255

//2. Number.isFinite 检测一个数值是否为有限数
console.log(Number.isFinite(100)); // true
console.log(Number.isFinite(100/0)); // false
console.log(Number.isFinite(Infinity)); // false

//3. Number.isNaN 检测一个数值是否为 NaN
console.log(Number.isNaN(123)); // false

//4. Number.parseInt Number.parseFloat字符串转整数
console.log(Number.parseInt('5211314love')); // 5211314
console.log(Number.parseFloat('3.1415926神奇')); // 3.1415926

//5. Number.isInteger 判断一个数是否为整数
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(2.5)); // false

//6. Math.trunc 将数字的小数部分抹掉
console.log(Math.trunc(3.5)); // 3

//7. Math.sign 判断一个数到底为正数 负数 还是零
console.log(Math.sign(100)); // 1
console.log(Math.sign(0)); // 0
console.log(Math.sign(-20000)); // -1

1.18 对象方法扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//1. Object.is 判断两个值是否完全相等 
console.log(Object.is(120, 120)); // true
console.log(Object.is(NaN, NaN)); // true
console.log(NaN === NaN); // false

//2. Object.assign 对象的合并
const config1 = {
host: 'localhost',
port: 3306,
name: 'root',
pass: 'root',
test: 'test'
};
const config2 = {
host: 'http://atguigu.com',
port: 33060,
name: 'atguigu.com',
pass: 'iloveyou',
test2: 'test2'
}
console.log(Object.assign(config1, config2)); // 后面对象的参数值会将前面的覆盖

//3. Object.setPrototypeOf 设置原型对象 Object.getPrototypeof
const school = {
name: '尚硅谷'
}
const cities = {
xiaoqu: ['北京','上海','深圳']
}
Object.setPrototypeOf(school, cities);
console.log(Object.getPrototypeOf(school));
console.log(school);

1.19 模块化

  • 模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。

  • 模块化的好处:

    • 防止命名冲突。
    • 代码复用。
    • 高维护性。
    • 模块化规范产品。
  • ES6之前的模块化规范有:

    • CommonJS ====> NodeJS、Browserify
    • AMD ====> requireJS
    • CMD ====> seaJS
  • 语法:

    • 模块功能主要有两个命令构成:exportimport
    • export命令用于规定模块的对外接口。
    • import命令用于输入其他模块提供的功能。
    1
    2
    3
    4
    5
    6
    // 分别暴露
    // 下面js代码放在./src/js/m1.js文件中
    export let school = '哆啦A梦'
    export function teach(){
    console.log('教技能')
    }
    1
    2
    3
    4
    5
    <!-- html代码 -->
    <script type="module">
    import * as m1 from "./src/js/m1.js"
    console.log(m1)
    </script>

1.19.1 暴露语法

1.19.1.1 分别暴露

1
2
3
4
5
6
// 分别暴露
// 下面js代码放在./src/js/m1.js文件中
export let school = '哆啦A梦'
export function teach(){
console.log('教技能')
}
1
2
3
4
5
<!-- html代码 -->
<script type="module">
import * as m1 from "./src/js/m1.js"
console.log(m1)
</script>

1.19.1.2 统一暴露

1
2
3
4
5
6
let school = '清华大学';
function findjob(){
console.log('找工作吧');
}
// 统一暴露
export {school, findjob}
1
2
3
4
5
6
<script type="module">  
import * as m1 from "./src/js/m1.js"
console.log(m1)
console.log(m1.school)
console.log(m1.findJob())
</script>
1652949959077

1.19.1.3 默认暴露(多变量暴露)

1
2
3
4
5
6
7
//默认暴露 export default
export default {
school:'清华大学',
change:function(){
console.log('可以改变人的一生!')
}
}
1
2
3
4
5
6
<script type="module">  
import * as m1 from "./src/js/m1.js"
console.log(m1.default)
console.log(m1.default.school)
console.log(m1.default.change())
</script>

1.19.2 引入语法

1.19.2.1 通用导入方式

1
2
3
4
5
6
7
8
9
<script type="module">
// 通用的导入方式
// 引入 m1.js 模块内容
import * as m1 from "./src/js/m1.js";
// 引入 m2.js 模块内容
import * as m2 from "./src/js/m2.js";
// 引入 m3.js
import * as m3 from "./src/js/m3.js";
</script>

1.19.2.2 解构赋值方式

1
2
3
4
5
<script type="module">
import {school,teach} from "./src/js/m1.js"
import {school as s,findJob} from "./src/js/m2.js"
import {default as m3 } from "./src/js/m3.js" // 针对默认暴露,必须写别名
</script>

1.19.2.3 简便形式(只针对默认暴露)

1
2
3
<script type="module">
import m3 from "./src/js/m3.js"
</script>

1.19.3 模块化方式2

1
2
3
4
5
// 下面js代码放在./src/js/app.js文件中
// 模块引入
import * as m1 from "./m1.js";
import * as m2 from "./m2.js";
import * as m3 from "./m3.js";
1
2
<!-- 引用 -->
<script src="./src/js/app.js" type="module"></script>

2、ECMASript 7新特性

2.1 Array.prototype.includes

  • Array.prototype.includes:用来检测数组中是否包含某个元素,返回布尔类型值。

  • 在ES7中引入指数操作符**,用来实现幂运算,功能与Math.pow结果相同。

    1
    2
    3
    4
    5
    6
    7
    8
    //include
    const mingzhu = ['西游记','红楼梦','水浒传','三国演义']
    console.log(mingzhu.includes('西游记')) //true
    console.log(mingzhu.includes('金瓶梅')) //false

    //**
    console.log(2**10) // 1024
    console.log(Math.pow(2, 10));

3、ECMASript 8新特性

3.1 async与await

3.1.1 async函数

  • 任意一个名称都是有意义的,先从字面意思来理解。async是“异步”的简写,而await可以认为是async wait的简写。所以应该很好理解async用于申明一个function是异步的,而await用于等待一个异步方法执行完成

async函数如何处理返回值

  • 运行以下代码示例:

    1
    2
    3
    4
    5
    6
    async function testAsync() {
    return "hello async";
    }

    const result = testAsync();
    console.log(result);
  • 所以,async函数返回的是一个Promise对象。async函数(包含函数语句、函数表达式、Lambda表达式)会返回一个Promise对象。如果在函数中return一个直接量,async会把这个直接量通过Promise.resolve()封装成Promise对象。

    Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将其封装成Promise实例。

  • async函数返回的是一个Promise对象,所以在最外层不能用await获取其返回值的情况下,我们当然应该用原来的方式:then() 链来处理这个Promise对象,就像这样:

    1
    2
    3
    testAsync().then(v => {
    console.log(v); // 输出 hello async
    });
  • 现在回过头来想下,如果async函数没有返回值,又该如何?很容易想到,它会返回Promise.resolve(undefined)

  • 联想一下Promise的特点——无等待,所以在没有await的情况下执行async函数,它会立即执行,返回一个 Promise对象,并且,绝不会阻塞后面的语句。这和普通返回Promise对象的函数并无二致。

3.1.2 await表达式

  • 一般来说,都认为await是在等待一个async函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是Promise对象或者其它值(换句话说,就是没有特殊限定)。

  • 因为async函数返回一个Promise对象,所以await可以用于等待一个async函数的返回值——这也可以说是await在等async函数,但要清楚,它等的实际是一个返回值。注意到await不仅仅用于等Promise对象,它可以等任意表达式的结果,所以,await后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function getSomething() {
    return "something";
    }

    async function testAsync() {
    return Promise.resolve("hello async");
    }

    async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
    }

    test();
  • await等到了它要等的东西,一个Promise对象,或者其它值,然后呢?我不得不先说,await是个运算符,用于组成表达式,await表达式的运算结果取决于它等的东西。

    • 如果它等到的不是一个Promise对象,那await表达式的运算结果就是它等到的东西。
    • 如果它等到的是一个Promise对象,await就忙起来了,它会阻塞后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果。这就是await必须用在async函数中的原因。async函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个Promise对象中异步执行。

    await等待的不是一个Promise Like对象的时候,相当于await Promise.resolve(...)

3.1.3 async/await整体使用

  • 上面已经说明了async会将其后的函数(函数表达式或Lambda)的返回值封装成一个Promise对象,而await会等待这个Promise完成,并将其resolve的结果返回出来

  • 现在举例,用setTimeout模拟耗时的异步操作,先来看看不用async/await会怎么写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function takeLongTime() {
    return new Promise(resolve => {
    setTimeout(() => resolve("long_time_value"), 1000);
    });
    }

    takeLongTime().then(v => {
    console.log("got", v);
    });
  • 如果改用async/await呢,会是这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function takeLongTime() {
    return new Promise(resolve => {
    setTimeout(() => resolve("long_time_value"), 1000);
    });
    }

    async function test() {
    const v = await takeLongTime();
    console.log(v);
    }

    test();
    • 发现takeLongTime()没有申明为async。实际上,takeLongTime()本身就是返回的Promise对象,加不加async结果都一样。

      如果一个函数本身就返回Promise对象,加async和不加async还是有一点点区别:加了async之后外面得到Promise对象并不是return的那一个,参阅代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      (() => {
      let promise;
      async function test() {
      promise = new Promise(resolve => resolve(0));
      promise.mark = "hello";
      return promise;
      }

      const gotPromise = test();
      console.log(`is same object?: ${promise === gotPromise}`); // false
      console.log(`promise.mark: ${promise.mark}`); // hello
      console.log(`gotPromise.mark: ${gotPromise.mark}`); // undefined
      })();

      了解这一点后,如果我们需要在返回的Promise对象上附加一些东西,比如cancel(),就得小心一点。

  • 又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对Promise对象的处理)差别并不明显,甚至使用async/await还需要多写一些代码,那它的优势到底在哪?

    • 单一的Promise链并不能发现async/await的优势,但是,如果需要处理由多个Promise组成的then链的时候,优势就能体现出来了(很有意思,Promise通过then链来解决多层回调的问题,现在又用async/await来进一步优化它)。

    • 假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用setTimeout来模拟异步操作:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      /**
      * 传入参数 n,表示这个函数执行的时间(毫秒)
      * 执行的结果是 n + 200,这个值将用于下一步骤
      */
      function takeLongTime(n) {
      return new Promise(resolve => {
      setTimeout(() => resolve(n + 200), n);
      });
      }

      function step1(n) {
      console.log(`step1 with ${n}`);
      return takeLongTime(n);
      }

      function step2(n) {
      console.log(`step2 with ${n}`);
      return takeLongTime(n);
      }

      function step3(n) {
      console.log(`step3 with ${n}`);
      return takeLongTime(n);
      }
    • 现在用Promise方式来实现这三个步骤的处理:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function doIt() {
      console.time("doIt");
      const time1 = 300;
      step1(time1)
      .then(time2 => step2(time2))
      .then(time3 => step3(time3))
      .then(result => {
      console.log(`result is ${result}`);
      console.timeEnd("doIt");
      });
      }

      doIt();

      输出结果resultstep3() 的参数700 + 200 = 900doIt()顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500毫秒,和console.time()/console.timeEnd()计算的结果一致。

    • 如果用async/await来实现呢,会是这样:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      async function doIt() {
      console.time("doIt");
      const time1 = 300;
      const time2 = await step1(time1);
      const time3 = await step2(time2);
      const result = await step3(time3);
      console.log(`result is ${result}`);
      console.timeEnd("doIt");
      }

      doIt();

      结果和之前的Promise实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样。

    • 现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function step1(n) {
      console.log(`step1 with ${n}`);
      return takeLongTime(n);
      }

      function step2(m, n) {
      console.log(`step2 with ${m} and ${n}`);
      return takeLongTime(m + n);
      }

      function step3(k, m, n) {
      console.log(`step3 with ${k}, ${m} and ${n}`);
      return takeLongTime(k + m + n);
      }
    • 这回先用async/await来写:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      async function doIt() {
      console.time("doIt");
      const time1 = 300;
      const time2 = await step1(time1);
      const time3 = await step2(time1, time2);
      const result = await step3(time1, time2, time3);
      console.log(`result is ${result}`);
      console.timeEnd("doIt");
      }

      doIt();
    • 除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成Promise方式实现会是什么样子?

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      function doIt() {
      console.time("doIt");
      const time1 = 300;
      step1(time1)
      .then(time2 => {
      return step2(time1, time2).then(time3 => [time1, time2, time3]);
      })
      .then(times => {
      const [time1, time2, time3] = times;
      return step3(time1, time2, time3);
      })
      .then(result => {
      console.log(`result is ${result}`);
      console.timeEnd("doIt");
      });
      }

      doIt();

      有没有感觉有点复杂的样子?那一堆参数处理,就是Promise方案的死穴——参数传递太麻烦了,看着就晕!

3.1.4 实际案例

  • async函数就是Generator函数的语法糖。

  • 创建一个Generator函数,依次读取两个文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var fs = require('fs');

    var readFile = function (fileName){
    return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
    if (error) reject(error);
    resolve(data);
    });
    });
    };

    var gen = function* (){
    var f1 = yield readFile('/etc/fstab');
    var f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
  • 写成async函数,就是下面这样。

    1
    2
    3
    4
    5
    6
    var asyncReadFile = async function (){
    var f1 = await readFile('/etc/fstab');
    var f2 = await readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
    • 一比较就会发现,async函数就是将Generator函数的星号(*)替换成 async,将yield替换成await,仅此而已。
  • await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    async function myFunction() {
    try {
    await somethingThatReturnsAPromise();
    } catch (err) {
    console.log(err);
    }
    }

    // 另一种写法
    async function myFunction() {
    await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
    });
    }
  • await命令只能用在async函数之中,如果用在普通函数,就会报错。

    1
    2
    3
    4
    5
    6
    7
    8
    async function dbFuc(db) {
    let docs = [{}, {}, {}];

    // 报错
    docs.forEach(function (doc) {
    await db.post(doc);
    });
    }
    • 上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。

      1
      2
      3
      4
      5
      6
      7
      8
      async function dbFuc(db) {
      let docs = [{}, {}, {}];

      // 可能得到错误结果
      docs.forEach(async function (doc) {
      await db.post(doc);
      });
      }

      上面代码可能不会正常工作,原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。

      1
      2
      3
      4
      5
      6
      7
      async function dbFuc(db) {
      let docs = [{}, {}, {}];

      for (let doc of docs) {
      await db.post(doc);
      }
      }
    • 如果确实希望多个请求并发执行,可以使用Promise.all方法。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));

      let results = await Promise.all(promises);
      console.log(results);
      }

      // 或者使用下面的写法
      async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));

      let results = [];
      for (let promise of promises) {
      results.push(await promise);
      }
      console.log(results);
      }

参考文档:https://segmentfault.com/a/1190000007535316http://www.ruanyifeng.com/blog/2015/05/async.html

  • async与await封装发送ajax请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // 发送 AJAX 请求, 返回的结果是 Promise 对象
    function sendAJAX(url) {
    return new Promise((resolve, reject) => {
    //1. 创建对象
    const x = new XMLHttpRequest();

    //2. 初始化
    x.open('GET', url);

    //3. 发送
    x.send();

    //4. 事件绑定
    x.onreadystatechange = function () {
    if (x.readyState === 4) {
    if (x.status >= 200 && x.status < 300) {
    //成功啦
    resolve(x.response);
    }else{
    //如果失败
    reject(x.status);
    }
    }
    }
    })
    }

    //promise then 方法测试
    // sendAJAX("https://api.apiopen.top/getJoke").then(value=>{
    // console.log(value);
    // }, reason=>{})

    // async 与 await 测试 axios
    async function main(){
    //发送 AJAX 请求
    let result = await sendAJAX("https://api.apiopen.top/getJoke");
    //再次测试
    let tianqi = await sendAJAX('https://www.tianqiapi.com/api/?version=v1&city=%E5%8C%97%E4%BA%AC&appid=23941491&appsecret=TXoD5e8P')

    console.log(tianqi);
    }

    main();

3.2 ES8对象方法扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//声明对象
const school = {
name:"尚硅谷",
cities:['北京','上海','深圳'],
xueke: ['前端','Java','大数据','运维']
};

//获取对象所有的键
console.log(Object.keys(school));
//获取对象所有的值
console.log(Object.values(school));
//entries
console.log(Object.entries(school));
//创建 Map
const m = new Map(Object.entries(school));
console.log(m.get('cities'));

// 对象属性的描述对象
console.log(Object.getOwnPropertyDescriptors(school));

// const obj = Object.create(null, {
// name: {
// //设置值
// value: '尚硅谷',
// //属性特性
// writable: true,
// configurable: true,
// enumerable: true
// }
// });

4、ECMASript 9新特性

4.1 Rest/Spread属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!-- 
Rest 参数与 spread 扩展运算符在 ES6 中已经引入,不过 ES6 中只针对于数组,
在 ES9 中为对象提供了像数组一样的 rest 参数和扩展运算符
-->
<script>
//rest 参数
function connect({host, port, ...user}){
console.log(host);
console.log(port);
console.log(user);
}

connect({
host: '127.0.0.1',
port: 3306,
username: 'root',
password: 'root',
type: 'master'
});


// 对象合并
const skillOne = {
q: '天音波'
}

const skillTwo = {
w: '金钟罩'
}

const skillThree = {
e: '天雷破'
}
const skillFour = {
r: '猛龙摆尾'
}

const mangseng = {...skillOne, ...skillTwo, ...skillThree, ...skillFour};

console.log(mangseng)

// ...skillOne => q: '天音波'
</script>

4.2 正则表达式命名捕获组

1
2
3
4
5
6
7
8
9
10
11
12
//声明一个字符串
let str = '<a href="http://www.atguigu.com">尚硅谷</a>';

//提取 url 与 『标签文本』
const reg = /<a href="(.*)">(.*)<\/a>/;

//执行
const result = reg.exec(str);

console.log(result);
// console.log(result[1]);
// console.log(result[2]);
1
2
3
4
5
6
7
let str = '<a href="http://www.atguigu.com">尚硅谷</a>';
//分组命名
const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/;

const result = reg.exec(str);

console.log(result);

4.3 正则表达式反向断言

1
2
3
4
5
6
7
8
9
10
//声明字符串
let str = 'JS5211314你知道么555啦啦啦';
//正向断言
const reg = /\d+(?=啦)/;
const result = reg.exec(str);

//反向断言
const reg = /(?<=么)\d+/;
const result = reg.exec(str);
console.log(result);

4.4 正则表达式dotAll模式

  • 正则表达式中点.匹配除回车外的任何单字符,标记『s』改变这种行为,允许行终止符出现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <script>
    //dot . 元字符 除换行符以外的任意单个字符
    let str = `
    <ul>
    <li>
    <a>肖生克的救赎</a>
    <p>上映日期: 1994-09-10</p>
    </li>
    <li>
    <a>阿甘正传</a>
    <p>上映日期: 1994-07-06</p>
    </li>
    </ul>`;
    //声明正则
    // const reg = /<li>\s+<a>(.*?)<\/a>\s+<p>(.*?)<\/p>/;
    const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;
    //执行匹配
    // const result = reg.exec(str);
    let result;
    let data = [];
    while(result = reg.exec(str)){
    data.push({title: result[1], time: result[2]});
    }
    //输出结果
    console.log(data);

    </script>

5、ECMASript 10新特性

5.1 对象扩展方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//将二维数组转换为对象
const result1 = Object.fromEntries([
['name','尚硅谷'],
['xueke', 'Java,大数据,前端,云计算']
]);
console.log(result1);

//Map
const m = new Map();
m.set('name','ATGUIGU');
const result2 = Object.fromEntries(m);
console.log(result2);

//Object.entries ES8,将对象转换为二维数组
const arr = Object.entries({
name: "尚硅谷"
})
console.log(arr);

5.2 字符串扩展方法

1
2
3
4
5
// trim
let str = ' iloveyou ';
console.log(str);
console.log(str.trimStart());
console.log(str.trimEnd());

5.3 flat与flatMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//flat 平
//将多维数组转化为低位数组
const arr = [1,2,3,4,[5,6]];
console.log(arr.flat())

const arr1 = [1,2,3,4,[5,6,[7,8,9]]];
// 参数为深度 是一个数字
console.log(arr1.flat());
console.log(arr1.flat(2));

//flatMap
const arr2 = [1,2,3,4];
const result = arr2.flatMap(item => [item * 10]);
console.log(result);

5.4 Symbol的description

1
2
3
//创建 Symbol
let s = Symbol('school');
console.log(s.description); // school

6、ECMASript 11新特性

6.1 私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person{
//公有属性
name;
//私有属性
#age;
#weight;
//构造方法
constructor(name, age, weight){
this.name = name;
this.#age = age;
this.#weight = weight;
}

intro(){
console.log(this.name);
console.log(this.#age);
console.log(this.#weight);
}
}

//实例化
const girl = new Person('晓红', 18, '45kg');

console.log(girl.name);
// console.log(girl.#age); // Uncaught SyntaxError: Private field '#age' must be declared in an enclosing class
// console.log(girl.#weight); // Uncaught SyntaxError: Private field '#age' must be declared in an enclosing class

girl.intro();

6.2 Promise.allSettled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//声明两个promise对象
const p1 = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('商品数据 - 1');
},1000)
});

const p2 = new Promise((resolve, reject)=>{
setTimeout(()=>{
// resolve('商品数据 - 2');
reject('出错啦!');
},1000)
});

// 调用allsettled方法:返回的结果始终是一个成功的,并且异步任务的结果和状态都存在。
const result = Promise.allSettled([p1, p2]);
console.log(result)

// 调用all方法:返回的结果是按照p1、p2的状态来的,如果都成功,则成功,如果一个失败,则失败,失败的结果是失败的Promise的结果。
const res = Promise.all([p1, p2]);
console.log(res);

6.3 String.prototype.matchAll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let str = `<ul>
<li>
<a>肖生克的救赎</a>
<p>上映日期: 1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期: 1994-07-06</p>
</li>
</ul>`;

//声明正则
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/sg

//调用方法
const result = str.matchAll(reg);

for(let v of result){
console.log(v);
}

const result1 = str.matchAll(reg);
const arr = [...result1];

console.log(arr);

6.4 可选链操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ?.相当于一个判断符,如果前面的有,就进入下一层级
function main(config){
// const dbHost = config && config.db && config.db.host;
const dbHost = config?.db?.host;

console.log(dbHost); // 192.168.1.100
}

main({
db: {
host:'192.168.1.100',
username: 'root'
},
cache: {
host: '192.168.1.200',
username:'admin'
}
})

6.5 动态import

1
2
3
4
// hello.js
export function hello(){
alert('Hello');
}
1
2
3
4
5
6
7
8
9
10
// import * as m1 from "./hello.js";
// 获取元素
const btn = document.getElementById('btn');

btn.onclick = function(){
// 使用之前并未引入,动态引入,import返回的其实是一个Promise对象
import('./hello.js').then(module=>{
module.hello();
})
}

6.6 BigInt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 大整形
let n = 521n;
console.log(n, typeof(n)); // 521n 'bigint'

// 函数
let n1 = 123;
console.log(BigInt(n1)); // 123n
// console.log(BigInt(1.2)); // Uncaught RangeError: The number 1.2 cannot be converted to a BigInt because it is not an integer

// 大数值运算
let max = Number.MAX_SAFE_INTEGER;
console.log(max); // 9007199254740991
console.log(max + 1); // 9007199254740992
console.log(max + 2); // 9007199254740992

console.log(BigInt(max)) // 9007199254740991n
console.log(BigInt(max) + BigInt(1)) // 9007199254740992n
console.log(BigInt(max) + BigInt(2)) // 9007199254740993n

6.7 绝对全局对象globalThis

1
console.log(globalThis) // 指向Window对象