一、概念

ECMAScript(ES):是一个国际通用性的标准化脚本语言。

JavaScript(JS):ECMAScript、DOM和BOM组成。

简单地理解为:ECMAScript是JavaScript的语言规范,JavaScript是ECMAScript的实现和扩展。

2011 年,ECMAScript 5.1 版发布。也就是ES5('use strict', forEach, map,filter.some...[Array.prototype], JSON.parse, JSON.stringfiy, object.defineProperty)。

2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。

目前已经发展至ES12。

二、为什么要学习ES?

  1. 精简代码,且主流浏览器已支持ES6(如果需要适配IE9-,不建议使用)。
  2. 行业内较新的前端框架(vue、react、原生微信小程序、uniapp、taro)都已经使用ES6+语法。
  3. 便于阅读框架源码。

三、常用ES6语法

  1. let、const

    限制在块级作用域({})的变量定义方式。

    let是新的var,但是声明的变量只能在当前块级作用域有效。

    const声明常量,常量只能赋值一次,一旦声明就不能改变。

    与var区别:

    1)var声明的变量为全局变量,默认绑定到windows上,全局作用域有效;而let仅在当前声明的块级作用域内有效。

    1. 在同一个块级作用域里,变量只能被let声明一次;在不同的块级作用域里,变量可以被let声明多次。var可以在全局作用域内声明多次。

    2. let严格遵守代码顺序,不存在变量提升。而var声明的变量,会忽略声明顺序,可以提升变量。

  2. 模板字符串

    模板字符串为构造字符串提供了语法糖。符号 ``

    let person = 'xiaoming';
    console.log(person + '是个好学生');
    // 等价于
    console.log(`${person}是个好学生`);
    
  3. 扩展(延展)运算符、rest运算符

    都是三个点 "..."。

    扩展(延展)运算符:用于把数组或者类数组对象展开形成一系列用逗号隔开的值。

    let getFunc = (x, y) => {
      console.log(x);
      console.log(y);
    }
    let arr = [1, 2];
    // 普通做法
    getFunc(arr[0], arr[1]);  // 打印 1, 2
    // 使用扩展运算符
    getFunc(...arr);// 打印1, 2
    

    使用场景

    1、给数组做深拷贝

    let arr1 = [1, 2];
    let arr2 = [...arr1];
    console.log(arr1 === arr2); // false
    

    2、合并数组,相当于ES5中的concat

    let arr1 = [1, 2];
    let arr2 = [3, 4];
    console.log(arr1.concat(arr2)); // 打印 [1, 2, 3, 4]
    console.log([...arr1, ...arr2]); // 打印 [1, 2, 3, 4]
    

    3、字符串转数组

    let str = 'hello';
    console.log([...str]); // 打印 ['h', 'e', 'e', 'l', 'o']
    

    rest运算符:把逗号隔开的值组合成一个数组 or 对象。

    使用场景

    1、不定参数传值

    // 不定参数传值 - 数组
    let getArrItem = ([...args]) => {
      console.log(args[0]);
      console.log(args[1]);
      console.log(args[2]);
      console.log(args[3]);
    }
    getArrItem([1, 2, 3])
    
    // 不定参数传值 - 对象
    let getObjItem = ({ ...args }) => {
      console.log(args.id);
      console.log(args.name);
    }
    getObjItem({ id: '1', name: 'xiaoming', address: '济南市' });
    

    2、与解构赋值关联使用,具体见解构赋值。

  4. 解构赋值

    允许基于模式匹配的方式进行赋值,这种模式匹配能够支持 数组和对象。

    对于数组:

    ​ 1、数组中的数据会自动解析到对应接受该值的变量中,数组的解构赋值要一一对应,否则就是undefined。

    ​ 2、可以直接省略元素,使用','做占位符。

    ​ 3、rest运算符 '...', 可以用于获取数组部分数据。

    // 数组
    let [ a, b, c, , d ] = [1, 2, 3, 4, 5]
    console.log('a:' + a)
    console.log('b:' +b)
    console.log('c:' +c)
    console.log(d)
    

    对于对象:

    ​ 1、对象与数组不同,对象不要求一一对应,但是要求变量必须与属性同名(不使用别名的情况),这样才能获取到正确的值。

    ​ 2、使用别名:let { local:address } = { local: '山东' } 可以访问address

    ​ 3、rest运算符 '...',可以用于获取对象部分数据。

    ​ 4、嵌套对象时与上述相同。

     // 对象
    let { id, name, address: local, ...otherinfo } =  { id: 1 , name: 'xiaoming',address: '济南市', sex: '男', note: ''  };
    console.log(id)
    console.log(name)
    console.log(local)
    console.log(otherinfo)
    let obj = { id: 2, ...otherinfo }
    console.log(obj)
    
    // 嵌套对象
    let { ids, otherinfos: { company } } = { ids: 2, otherinfos: { company: 'jxd' } };
    console.log(company)
    
  5. 箭头函数

    类似于匿名函数,将原函数的"function"关键字和函数名都去掉,并且用"=>"连接参数列表和函数体。

    function add(x, y) {
    	return x + y
    }
    // 等价于
    let add = (x, y) => {
      return x + y
    }
    // 等价于
    let add = (x, y) => x + y
    console.log(add(1,2));
    

    注意点:

    1. 函数的参数只有一个时,可以省略 ()。

    ​ 函数体内只有一条return语句,且返回非对象时,可以省略 return关键字和{};当返回对象时,需要加(),避免和函数体的{...}有语法冲突。

    // 返回非对象
    let getNum = (x)=> { return x };
    // 等价于
    let getNum = x => x;
    
    // 返回对象
    let getObj = name => ({ name: name });
    console.log(getObj('xiaoming'));
    

    2)箭头函数的this指向就是上下文里对象this的指向,如果没有上下文对象,this就指向window。不受call、apply、bind等方法的影响。

    // 关于this指向
    let obj = {
      id: '1',
      age: this, // 指向window
      name: () => { 
        console.log(this); // 指向window
        setTimeout(() => {
          console.log(this);// 指向window
        })},
      getFunc: function() {
        console.log(this); // 指向obj
        setTimeout(() => {
          console.log(this); // 指向obj
        }) 
      }
    }
    obj.age;
    obj.name();
    obj.getFunc();
    
  6. 类(class)

    ​ 类(class)是在基于原型的面向对象模式上简单包装的语法糖。拥有一个 单一且方便的声明形式将更易于使用,并且 鼓励混合使用。类(class)支持基于原型的继承、super 调用、 实例和静态方法以及构造函数。

    等价于构造函数。

    1、基本格式

    ​ 1) class后面紧跟方法名,方法名需要大写。

    ​ 2) 创建实例时,使用new Xxx()的格式。

    ​ 3) 都存在constructor,即构造函数。

    // 声明类
    class Person {
      constructor(name) {
        this.name = name;
      }
      getPersonInfo() {
        console.log(this.name)
      }
    }
    
    // 创建实例
    let person = new Person('xiaoming');
    person.getPersonInfo();
    

    2、继承

    ​ 1) 使用关键字 extends。

    ​ 2) constructor中必须使用super(),用于继承父元素的属性和方法。

    // 声明类
    class BoyPerson extends Person {
      constructor(name, sex) {
        super(name);
        this.sex = sex;
      }
    
      getBoyPersonInfo() {
        console.log(`姓名: ${this.name}, 性别: ${this.sex}`);
      }
    }
    
    // 创建实例
    let boy = new BoyPerson('xiaoli', '男')
    boy.getBoyPersonInfo();
    

    3、实例属性

    ​ 1) 定义在constructor()方法中的属性都是对象的属性,可以用this获取。

    ​ 2) 实例属性也可以定义在类顶层的,定义格式 _xxx, 通过this获取。

    4、静态属性和方法

    ​ 所有在类中定义的属性和方法,都会被实例继承。如果在属性和方法前面添加static,则该属性和方法不会被实例继承,而是直接通过类来调用,这就是静态方法。

    ​ 1) 使用关键字 static。

    ​ 2) 不能通过实例调用,只能通过类调用,即构造函数.方法名。

    5、私有属性和方法

     只能在类的内部访问的属性和方法,外部不能调用。
    

    ​ 写法: 在属性或者方法前面加 #。

    ​ 内部访问:需要 this.#属性名|方法名。

    class BoyPerson extends Person {
      _local = '济南市';
      #age = 10;
      constructor(name, sex) {
        super(name);
        this.sex = sex;
      }
    
      getBoyPersonInfo() {
        console.log(`姓名: ${this.name}, 性别: ${this.sex}, 地址: ${this._local}, 年龄${this.#age}`);
      }
    }
    
    let boy = new BoyPerson('xiaoli', '男')
    boy.getBoyPersonInfo();
    console.log(boy._local); // 打印 济南市
    console.log(boy.#age); // 报错
    

    PS:

    ​ 1)类声明和函数声明一个重要的区别,类声明不会在作用域内导致变量提升。所以在使用类的时候先声明后创建实例。

    ​ 2) constructor中定义的内容都会绑定到对象的属性上,类的所有方法会定义prototype上,例如constructor()、getBoyPersonInfo() 会绑定到构造函数的prototype上。

    ​ 3) 添加方法确实可以通过 Person.prototype.add = () { return 1 } || person.proto.add = () => { return 1 }。但是不推荐,会影响其他实例。

    class Person {
    	static comeFrom = 'china'
      constructor(name) {
        this.name = name;
      }
      getPersonInfo() {
        console.log(this.name)
      }
    }
    
    let person = new Person('xiaoming');
    let person2 = new Person('xiaoli');
    person.__proto__.getName = () => { console.log(this.name) };
    
  7. Promise

    ​ 构造函数,异步操作容器,里面存在一个异步操作。通过Promise可以获取该异步操作可能发生的结果,比如pending, fulfilled, rejected。

    1、Promise对象特点

    ​ 1)提供三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败),只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个操作。

    ​ 2)状态变化有两种,从pending -> fulfilled || pending -> rejected。

    2、作用

    ​ 1) 异步操作以同步的写法表现出来,避免回调函数层层嵌套 .then().catch().finally()。

    ​ 2) Promise对象提供了许多API,使异步函数处理更加容易。

    3、基本用法

    ​ 1) 创建promise实例。Promise构造函数接收一个函数作为参数,该函数中有两个参数,分别是resolve,reject。

    ​ 2) 调用Promise提供方法。Promise对象生成后,可以用then() 指定resolve(必选)和reject(可选)状态的回调函数。

    ​ 3) 如果resolve()带有参数,该参数会被传递给回调函数。如果reject() 带有参数,该参数一般是Error对象的实例,也会被传递给回调函数。

    const promise = new Promise((resolve, reject) => {
        // todo 这里处理逻辑
        if(success) {
            // 成功时执行
            resolve(value)
        } else {
            // 失败时执行
            reject(value)
        }
    })
    promise.then(value => {
    
    }, err => {
    
    })
    

    4、Promise实例提供的方法

    ​ 1) Promise.all(iterator),其中iterator必须是个可迭代的对象,比如Array 或者 String,返回一个Promise实例

    ​ 传入参数中任意一个primise返回失败,则整体返回失败,返回的错误信息为第一个失败的promise结果。

    ​ 2) Promise.race(iterator)。返回最先发生改变的实例。

    ​ 3) Promise.resolve(value): 返回一个以给定的值解析后的Promise对象。

    ​ 4) Promise.reject(reason): 返回一个含有reason的状态为rejected的Promise对象。

    ​ 5) Promise.prototype.then(): 为Promise实例状态发生变化时添加回调函数,接受两个参数,fulfilled状态时的回调函数,rejected状态时的回到函数。并且该方法返回一个新的Promise对象。

    ​ 6) Promise.prototype.finally(): 返回一个Promise, 在上一轮promise运行后,无论状态是fullfilled还是rejected,都会执行finally中指定的回调函数。

    ​ 7) Promise.prototype.catch(): 返回一个Promise,并且处理拒绝情况,与then中的err相同。这里推荐使用catch方法,不要在then() 中定义rejected状态的回调函数,这是因为catch还可以捕捉在then()方法执行中存在的错误。

    getPreApprovalList(params) {
        return new Promise((resolve, reject) => {
            OtherContractService.getPreApprovalList(params).then(data => {
                (data.list || []).forEach(i => this.$set(i, 'deleteLoading', false))
                    resolve(data)
                }).catch(e => {
                    reject(e)
            })
        })
    }
    

    promise具有then(),返回一个新的promise实例,因此可以采用链式写法,即then方法后面再调用then。

    5、关于Promise执行顺序

    // 执行顺序
    function timeOut(ms) {
      return new Promise((resolve, reject) => {
        console.log(4)
        return setTimeout(resolve, ms, '5' )
      })
    }
    
    timeOut(100).then(val => { console.log(val) }, err => { console.log('失败') })
     
    let promise = new Promise((resolve, reject) => {
      resolve(2); // 这里比较下加return的情况
      console.log(1);
    })
    
    promise.then(val => { console.log(val) })
    console.log(3)
    

    解析:

    1、创建Promise实例时,优先执行Promise实例中的console。

    2、执行同步console。

    3、从上往下执行不含延时的Promise的resolve or reject。

    4、从上往下执行含有延时的Promise的resolve or reject。

    timeOut是个含有返回Promise对象的方法,当执行timeOut(100)时,会生成Promise实例,这里面的4优先打印,then()暂缓;

    let promise = new Promise({...}),这时候Promise对象已经创建,所以接下来打印1,then()暂缓;

    打印同步的3;

    从上往下数,第二个promise没有延时,优先打印2。

    最后打印5。

    6、实现异步加载图片

    let url = "图片地址";
    function loadImageAsync(url) {
      return new Promise((resolve, reject) => {
        var image = new Image();
        image.src = url;
        image.onload = function() {
          resolve(image);
        }
        image.onerror = function() {
          reject('加载失败')
        }
      })
    }
    
    loadImageAsync(url).then(val => {})
    

    7、总结

    ​ 1) 什么是Promise?

    ​ Promise() 其实是个构造函数,用户可以根据操作状态给与处理。Promise()的prototype提供了多个api,比如all()、race()、then()、catch()、finally(),给实例化的promise对象提供了操作、执行、异常捕捉、后续执行这些方法。

    ​ 2)与普通回调函数的区别。

    ​ 在执行多项异步操作时,普通的回调函数需要在回调中嵌套回调,代码比较臃肿,容易出现回调地狱。通过promise,可以在then()中resolve返回下一个异步操作,then()返回的是一个新的promise对象,原型对象上存在then(),可以链式调用。

  8. 数据结构 Set

    构造函数,类似于数组,但是成员的值都是唯一的,可以用于去重(仅适用于普通数组,不适用于对象数组)。

    数组去重方法:[ ...new Set(arr) ]

    1)Set的属性及方法

    ​ constructor: 构造函数,返回Set();

    ​ size: 返回实例的成员总数

    ​ add(val): 添加某个值,返回Set结构本身

    ​ delete(val): 删除某个值,返回boolean;

    ​ has(val): 查找是否含有某个值,返回boolean;

    ​ clear(): 清除所有指,没有返回值。

    2) Set遍历操作

    ​ keys(): 返回键名的遍历器

    ​ values(): 返回键值的遍历器

    ​ entries(): 返回键值对的遍历器

    ​ forEach((value, keys) => {})

  9. 数据结构Map

    构造函数,类似于Object,{ key: value }的形式,但是和Object有区别

    Object:只能用字符串当key, "字符串 -> 值"

    Map: 键的范围不限于字符串,各种类型的值(包括对象)都可以当做key,"值 -> 值";

    const map = new Map([['name', 'xiaoming'], ['title', 'author']])
    

    1)Map的属性和方法

    ​ size: 返回Map结构的成员总数

    ​ set(key, value): 设置键名为key的对应键值为value,然后返回整个Map。

    ​ get(key): 读取键值为key的键值

    ​ has(key): 判断某个键是否在当前的Map对象中,返回Boolean。

    ​ delete(key): 删除某个值,返回boolean

    ​ clear(): 清空Map对象

四、ES7~ES12部分常用语法

  1. async 、await

    异步终极解决方案。以同步的写法完成异步操作流程,避免异步嵌套。

    async: 表示函数里有异步操作。

    await: 表示跟随在后面的表达式需要等待结果, await 后面跟着Promise对象 。

    当函数执行时一旦遇到await就返回,等到触发的异步操作完成,再接着执行函数体后面的语句。

    let timeOut = ms => {
      return new Promise((resolve, reject) => {
     return setTimeout(resolve, ms, 'timeOut')
      })
    }
    // 打印顺序 timeOut, 1
       let asyncMethod = async () => {
         try {
           let timeConsole = await timeOut(2000);
           console.log(timeConsole); 
           console.log(1)
         } catch(e) {
           console.log(e)
         }
       }
    
       asyncMethod();
    

    注意点:

    await 后面的Promise对象,运行有可能返回reject,最好把await放在try...catch里,这样能够捕捉异常。

    await只能用在async函数中,如果是普通函数就会报错。

    可以使用 promise.all 方法发起多个请求。

async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));

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

}

// 或者使用下面的写法 ES9 异步迭代 await可以和for...of循环一起使用,以串行的方式运行异步操作
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);

}


当出现多个Promise

```javascript
let timeOut1 = ms => {
  return new Promise((resolve, reject) => {
    return setTimeout(resolve, ms, 'timeOut1')
  })
}

let timeOut2 = ms => {
  return new Promise((resolve, reject) => {
    return setTimeout(resolve, ms, 'timeOut2')
  })
}

let timeOut3 = ms => {
  return new Promise((resolve, reject) => {
    return setTimeout(resolve, ms, 'timeOut3')
  })
}

let funcArr = [ timeOut1(1000), timeOut2(2000), timeOut3(3000) ]
async function promiseFunc() {
  console.time();
  let result = await Promise.all(funcArr);
  console.log(result);
  console.timeEnd();
  console.log('结束')
}

promiseFunc();
  1. Array.prototype.includes

    返回Boolean,表示某个数组是否包含指定值。

    let arr = [1, 2, 3];
    if(arr.includes(1)) {
    	console.log('包含')
    }
    
  2. String.trimStart()和String.trimEnd()

    去除字符串首尾空白字符。

  3. 空值处理 ??

    表达式在 ?? 的左侧 运算符求值为undefined或null,返回其右侧。

  4. Optional chaining(可选链)

    ?.用户检测不确定的中间节点

     let person = {};
     // let childName = person.children.name; // 报错  Cannot read property 'name' of undefined
     let childName = person.children ?. name; // undefined
     console.log(childName); 
    

五、参考

Babel官网中文文档:https://www.babeljs.cn/docs/learn

Babel官网中文文档:https://www.liaoxuefeng.com/wiki/1022910821149312

阿西河ECMAScript 文档说明:https://www.axihe.com/api/js-es/api/api.html

六、补充

  1. 原型、原型链关系