[复习面试资料] es6,es7部分新特性归纳整理

1,571 阅读25分钟

一. 简介

        现在网上很多es6,es7的资料整理以及深入分析,而这篇文章算是一个比较简单的归纳整理,比较简单,主要涵盖一些面试当中最多问道的一些问题,不过如果后期有时间会整理出来一篇更加详细的,例如数组去重的方法归纳大全之类的.欢迎各位大神批评指正.

二. es6

1. 声明表达式let与const


1.1 let声明表达式

let 命令的用法和 var 相似,但是 let 只在所在代码块内有效。
基础用法

    let a = 1;
    let b = 2;

并且 let 有以下特点:

  • 不存在变量提升:
    在ES6之前,我们 var 声明一个变量一个函数,都会伴随着变量提升的问题,导致实际开发过程经常出现一些逻辑上的疑惑,按照一般思维习惯,变量都是需要先声明后使用。
// var 
console.log(v1); // undefined
var v1 = 2;
// 由于变量提升 代码实际如下
var v1;
console.log(v1)
v1 = 2;

// let 
console.log(v2); // ReferenceError
let v2 = 2;
  • 不允许重复声明:
    letconst相同作用域下,都不能重复声明同一变量,并且不能在函数内重新声明参数
  • // 1. 不能重复声明同一变量
    // 报错
    function f1 (){
        let a = 1;
        var a = 2;
    }
    // 报错
    function f2 (){
        let a = 1;
        let a = 2;
    }
    
    // 2. 不能在函数内重新声明参数
    // 报错
    function f3 (a1){
        let a1; 
    }
    // 不报错
    function f4 (a2){
        {
            let a2
        }
    }
    

    1.2 const声明表达式

    const 声明一个只读常量
    基础用法

    const PI = 3.1415926;
    PI = 3; 
    // TypeError: Assignment to constant variable.
    
    • const 声明时,必须赋值;
    const a ; 
    // SyntaxError: Missing initializer in const declaration.
    
    • const 声明的常量,let 不能重复声明;
    const PI = 3.1415926;
    let PI = 0;  
    // Uncaught SyntaxError: Identifier 'PI' has already been declared
    

    1.3 let和const声明表达式总结

            1.3.1let 和const都是块级作用域(var没有会计作用域),并且都不会有变量提升(var有变量提升)

            1.3.2const声明的变量都会被认为是常量,不能被修改,但如果被const修饰的是对象,对象中的属性值可以被修改

            1.3.3et在同一个块中不能定义两个相同名称的变量(var可以定义多个)

    2.解构与赋值


    先讲一个普通的例子:

    var people = {
        name: 'lux',
        age: 20
    }
    //es5
    var name = people.name
    var age = people.age
    //es6
    const {name, age} = people
    
    //数组同样适用
    
    var arr = [1,2,3];
    const {one, , three} = arr;
    

    2.1 对象的解构赋值

    与数组解构不同的是,对象解构不需要严格按照顺序取值,而只要按照变量名去取对应属性名的值,若取不到对应属性名的值,则为undefined

    注意点

    • 变量名属性名不一致,则需要修改名称。
    let {a:b} = {a:1, c:2}; 
    // error: a is not defined
    // b => 1
    

    对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
    上面代码中,a 是匹配的模式,b才是变量。真正被赋值的是变量b,而不是模式a

    • 对象解构也支持嵌套解构
    let obj = {
        a:[ 1, { b: 2}]
    };
    let {a, a: [c, {b}]} = obj;
    // a=>[1, {b: 2}], b => 2, c => 1
    

    指定解构的默认值

    let {a=1} = {};        // a => 1
    let {a, b=1} = {a:2};  // a => 2, b => 1
    
    let {a:b=3} = {};      // b => 3
    let {a:b=3} = {a:4};   // b = >4
    // a是模式,b是变量 牢记
    
    let {a=1} = {a:undefined};  // a => 1
    let {a=1} = {a:null};   // a => null
    // 因为null与undefined不严格相等,所以赋值有效
    // 导致默认值1不会生效。
    

    2.2 字符串的解构赋值

    字符串的解构赋值中,字符串被转换成了一个类似数组的对象基础用法

    const [a, b, c, d, e] = 'hello';
    a // "h"
    b // "e"
    c // "l"
    d // "l"
    e // "o"
    
    let {length:len} = 'hello';// len => 5
    

    2.3 数值和布尔值的解构赋值

    解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错。

    let {toString: s} = 123;
    s === Number.prototype.toString // true
    let {toString: s} = true;
    s === Boolean.prototype.toString // true
    
    let { prop: x } = undefined; // TypeError
    let { prop: y } = null;      // TypeError
    

    2.4 函数参数的解构赋值

    基础用法

    function fun ([a, b]){
        return a + b;
    }
    fun ([1, 2]); // 3
    

    指定默认值的解构:

    function fun ({a=0, b=0} = {}){
        return [a, b];
    }
    fun ({a:1, b:2}); // [1, 2]
    fun ({a:1});      // [1, 0]
    fun ({});         // [0, 0]
    fun ();           // [0, 0]
    
    function fun ({a, b} = {a:0, b:0}){
        return [a, b];
    }
    fun ({a:1, b:2}); // [1, 2]
    fun ({a:1});      // [1, undefined]
    fun ({});         // [undefined, undefined]
    fun ();           // [0, 0]
    

    2.5 应用

    • 交换变量的值:
    let a = 1,b = 2;
    [a, b] = [b, a]; // a =>2 , b => 1 
    
    • 函数返回多个值:
    // 返回一个数组
    function f (){
        return [1, 2, 3];
    }
    let [a, b, c] = f(); // a=>1, b=>2, c=>3
    
    // 返回一个对象
    function f (){
        return {a:1, b:2};
    }
    let {a, b} = f();    // a=>1, b=>2
    
    • 快速对应参数: 快速的将一组参数与变量名对应。
    function f([a, b, c]) {...}
    f([1, 2, 3]);
    
    function f({a, b, c}) {...}
    f({b:2, c:3, a:1});
    
    • 提取JSON数据
    let json = {
        name : 'leo',
        age: 18
    }
    let {name, age} = json;
    console.log(name,age); // leo, 18
    
    • 遍历Map结构:
    const m = new Map();
    m.set('a',1);
    m.set('b',2);
    for (let [k, v] of m){
        console.log(k + ' : ' + v);
    }
    // 获取键名
    for (let [k] of m){...}
    // 获取键值
    for (let [,k] of m){...}
    
    • 输入模块的指定方法: 用于按需加载模块中需要用到的方法。
    const {log, sin, cos} = require('math');
    

    3.深拷贝与浅拷贝


    • Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
    const object1 = {
      a: 1,
      b: 2,
      c: 3
    };
     
    const object2 = Object.assign({c: 4, d: 5}, object1);
    console.log(object2);
     
    Object { c: 3, d: 5, a: 1, b: 2 }
     
    // 这是浅拷贝,返回的不是一个新对象,而是把一个或多个源对象添加到目标对象
    
    • 结构赋值
    let object1 = {
      a: 1,
      b: 2,
      c: 3
    };
     
    let object2 = {...object1};
    object1.a=11;
     
    console.log(object2);
    Object { a: 11, b: 2, c: 3 }
     
    // 这也是浅拷贝
    
    
    • 最简单的深拷贝(JSON.stringify() 和JSON.parse())
    • 先把对象使用JSON.stringify()转为字符串,再赋值给另外一个变量,然后使用JSON.parse()转回来即可。
    
    let object1 = {
      a: 1,
      b: 2,
      c: 3
    };
     
    let object2 =JSON.parse( JSON.stringify(object1));
    object2.a=11;
     
    console.log(object1,object2);
     
    Object { a: 1, b: 2, c: 3 } Object { a: 11, b: 2, c: 3 }
    

    4.Set和Map数据结构


    4.1 Set

    介绍:
    Set数据结构类似数组,但所有成员的值唯一
    Set本身为一个构造函数,用来生成Set数据结构,使用add方法来添加新成员。

    let a = new Set();
    [1,2,2,1,3,4,5,4,5].forEach(x=>a.add(x));
    for(let k of a){
        console.log(k)
    };
    // 1 2 3 4 5
    

    基础使用

    let a = new Set([1,2,3,3,4]);
    [...a]; // [1,2,3,4]
    a.size; // 4
    
    // 数组去重
    [...new Set([1,2,3,4,4,4])];// [1,2,3,4]
    

    注意

    • Set中添加值的时候,不会类型转换,即5'5'是不同的。
    [...new Set([5,'5'])]; // [5, "5"]
    

    属性和方法

    • 属性:

      • Set.prototype.constructor:构造函数,默认就是Set函数。
      • Set.prototype.size:返回Set实例的成员总数。
    • 操作方法:

      • add(value):添加某个值,返回 Set 结构本身。
      • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
      • has(value):返回一个布尔值,表示该值是否为Set的成员。
      • clear():清除所有成员,没有返回值。
    let a = new Set();
    a.add(1).add(2); // a => Set(2) {1, 2}
    a.has(2);        // true
    a.has(3);        // false
    a.delete(2);     // true  a => Set(1) {1}
    a.clear();       // a => Set(0) {}
    
    

    数组去重

    let a = new Set([1,2,3,3,3,3]);
    
    

    4.2 Map

    由于传统的JavaScript对象只能用字符串当做键,给开发带来很大限制,ES6增加Map数据结构,使得各种类型的值(包括对象)都可以作为键。
    Map结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。 基础使用

    let a = new Map();
    let b = {name: 'leo' };
    a.set(b,'my name'); // 添加值
    a.get(b);           // 获取值
    a.size;      // 获取总数
    a.has(b);    // 查询是否存在
    a.delete(b); // 删除一个值
    a.clear();   // 清空所有成员 无返回
    
    

    注意

    • 传入数组作为参数,指定键值对的数组
    let a = new Map([
        ['name','leo'],
        ['age',18]
    ])
    
    • 如果对同一个键多次赋值,后面的值将覆盖前面的值
    let a = new Map();
    a.set(1,'aaa').set(1,'bbb');
    a.get(1); // 'bbb'
    
    • 如果读取一个未知的键,则返回undefined
    new Map().get('abcdef'); // undefined
    
    • 同样的值的两个实例,在 Map 结构中被视为两个键。
    let a = new Map();
    let a1 = ['aaa'];
    let a2 = ['aaa'];
    a.set(a1,111).set(a2,222);
    a.get(a1); // 111
    a.get(a2); // 222
    

    遍历方法: Map 的遍历顺序就是插入顺序。

    • keys():返回键名的遍历器。
    • values():返回键值的遍历器。
    • entries():返回所有成员的遍历器。
    • forEach():遍历 Map 的所有成员。
    let a = new Map([
        ['name','leo'],
        ['age',18]
    ])
    
    let a1 = [...a.keys()];   // a1 => ["name", "age"]
    let a2 = [...a.values()]; // a2 =>  ["leo", 18]
    let a3 = [...a.entries()];// a3 => [['name','leo'], ['age',18]]
    
    

    4.3 Set进行数组去重

    通过上面的讲述应该明白了Set的用法,那么话不多说直接上代码

     let a=[1,2,3,4,5,6,1,2,3,2,1,2]
          let b=new Set()
          let c=[];
          a.forEach(function (value,key,arr){
            b.add(value)
          })
          b.forEach(function (value,key,set) {
            c.push(value)
          })
          console.log(c)
          // [1,2,3,4,5,6]
    

    5.Promise,async/await


    5.1 Promise

    5.1.1 Promise 是什么

    Promise 是一种规范,在es6以前,浏览器没有内置的promise,不同框架有各自的promise实现,本文中的Promise以es6中的Promise为例。

    一个Promise封装了一个操作(异步或同步)的结果和其状态,按我的理解来看,它的出现有以下几点原因,一方面是为了解决回调函数的嵌套问题,如果没有promise 我们可以想象一下,应该如何控制流程,假设有以下场景,执行操作A,如果失败了,执行 操作B,执行操作C,同时B 和 C 失败和成功了也会有对应的处理,这里为了简化,统一使用操作D来处理最终结果,那么最后可能会写出如下的代码

    function stepA(arg, onSuccess, onFailure) {
       var output = dosomethingA(arg);
       var res = output.res;
       if (output.success) {
           onSuccess(res);
       } else {
           onFaliure(res);
       }
    }
    
    function stepB(arg, onSuccess, onFailure) {
       var output = dosomethingB(arg);
       var res = output.res;
       if (output.success) {
           onSuccess(res);
       } else {
           onFaliure(res);
       }
    }
    
    function stepC(arg, onSuccess, onFailure) {
       var output = dosomethingC(arg);
       var res = output.res;
       if (output.success) {
           onSuccess(res);
       } else {
           onFaliure(res);
       }
    }
    
    function stepD(arg) {
       dosomethingD(arg);
    }
    
    stepA('test', function(res) {
       stepB(res, stepD, stepD);
    }, function(res) {
       stepC(res, stepD, ste
    

    这里如果流程链更长或者将其中的几个step换成是匿名函数的话,就会出现更多的回调嵌套,同时代码会非常的长,根本无法阅读。 还有一个问题是,人们习惯于用线性的思想去思考为题,promise这种链式调用的模式可以将异步代码看起来像同步代码一样执行。

    5.1.2 Promsie 的用法

    还是刚刚的那个例子,如果用Promise该怎么改写

    function executeStep(arg, dosomething) {
        return new Promise(function(resolve, reject) {
            var result = dosomething(arg);
            var res = result.res;
            if (result.success) {
                resolve(res);
            } else {
                reject(res);
            }
        });
    }
    
    function dosomethingA(arg) {
        // ...
        return result;
    }
    
    function dosomethingB(arg) {
        // ...
        return result;
    }
    
    function dosomethingC(arg) {
        // ...
        return result;
    }
    
    function dosomethingD(arg) {
        // ...
        console.log(arg);
    }
    
    function wrap(dosomething, result) {
        return executeStep(result, dosomething);
    }
    
    wrap(dosomethingA, 'test').then(function(result) {
        return wrap(dosomethingB, result);
    }, function(reason) {
        return wrap(dosomethingC, reason);
    }).then(dosomethingD, dosomethingD);
    

    根据PromiseA+规范,Promise有3种状态,分别是pending,rejected和fullfilled,其中rejected 和fullfilled又称为settled,promise可由pending转到settled,处于settled状态的promise状态不可再变化。

    5.1.3 Promise常用API

    Promise.prototype.constructor

    var promise = new Promise(function(resolve, reject) {
        setTimeout(function() {
            var res = Math.random();
            if (res < 0.5) {
                resolve(res);
            } else {
                reject(res);
            }
        });
    });
    

    其内部实现大体如下

    function MyPromise(resolver){
            if(typeof resolver !== 'function'){
                throw new TypeError(resolver + ' is not a function');
            }
            if(!(this instanceof MyPromise) return new MyPromise(resolver);
    
            var self = this;
    
            self.status_ = 'PENDING';
            self.data_ = undefined;
            self.resolveCallBacks = [];
            self.rejectCallBacks = [];
    
            function resolve(value){
                setTimeout(function(){
                    if(self.status_ === 'PENDING'){
                        self.status_ = 'RESOLVED';
                        self.data_ = value;
                        for(var i=0; i<self.rejectCallBacks.length; i++){
                            self.rejectCallBacks[i](value);
                        }
                    }
                });
            }
    
            function reject(reason){
                setTimeout(function(){
                    if(self.status_ === 'PENDING'){
                        self.status_ = 'REJECTED';
                        self.data_ = reason;
                        for(var i=0; i<self.resolveCallBacks.length; i++){
                            self.resolveCallBacks[i](value);
                        }
                    }
                });
            }
    
            try{
                resolver(resolve, reject);
            }catch(reason){
                reject(reason);
            }
        }
    

    当通过构造函数创建一个Promise的时候,首先会将其状态设置为PENDING,同时初始化resolve和reject的回调数组,在这里,resolvereject都是异步调用的,只有当当前Promise状态为PENDING时,rejectresolve内部的代码才会执行, 更改Promise的状态,将结果设置为Promise的值,同时遍历对应的回调数组并执行。
    Promise.prototype.then(onFulfilled, onRejected)
    这个是Promise中使用频率最高的一个方法了,它会更具Promise的状态选择执行回调或将回掉添加到Promise的回调数组上,大致实现如下

    MyPromise.prototype.then = function(onResolved, onRejected) {
            onResolved = typeof onResolved === 'function' ? onResolved : function(value){return value};
            onRejected = typeof onRejected === 'function' ? onRejected : function(reason){throw reason};
    
            var self = this;
            var newPromise;
            if(self.status_ === 'RESOLVED'){
                newPromise = new MyPromise(function(resolve, reject){
                    setTimeout(function(){
                        try{
                            var x = onResolved(self.data_);
                            resolvePromise(newPromise, x, resolve, reject);
                        }catch(reason){
                            reject(reason);
                        }
                    });
                });
            }else if(self.status_ === 'REJECTED'){
                newPromise = new MyPromise(function(resolve, reject){
                    setTimeout(function(){
                        try{
                            var x = onRejected(self.data_);
                            resolvePromise(newPromise, x, resolve, reject);
                        }catch(reason){
                            reject(reason);
                        }
                    });
                });
            }else{
                newPromise = new MyPromise(function(resolve, reject){
                    self.resolveCallBacks.push(function(){
                        try{
                            var x = onResolved(self.data_);
                            resolvePromise(newPromise, x, resolve, reject);
                        }catch(reason){
                            rejcet(reason);
                        }
                    });
                    self.rejectCallBacks.push(function(){
                        try{
                            var x = onRejected(self.data_);
                            resolvePromise(newPromise, x, resolve, reject);
                        }catch(reason){
                            reject(reason);
                        }
                    });
                });
            }
            return newPromise;
        };
    

    首先会对传入的回调函数进行检测,如果不是function的话就会使用默认的function(这里的resolvePromise将会在下文讲到),之后判断当前Promise的状态,如果Promise已经决议,则将当前Promise的值传给对应的回调,同时执行resolvePromise,这些操作会在新创建的Promise中通过异步的方式进行执行,如果当前Promise处于PENDING状态,则会将回调添加到当前Promise的回调数组中。下面看一下resolvePromise

    function resolvePromise(mPromise, x, resolve, reject){
            var then;
            var thenCalledOrThrow = false;
            if(mPromise === x){
                return reject(new TypeError('Circle is not expected to exist'));
            } // Promises/A+ 2.3.1
    
            if(x instanceof MyPromise){ // 2.3.2
                if(x.status_ === 'PENDING'){
                    x.then(function(value){
                        resolvePromise(mPromise, value, resolve, reject)
                    }, reject);
                }else{
                    x.then(resolve, reject);
                }
                return;
            }
    
            if((x != null) && ((typeof x === 'object') || (typeof x === 'function'))){ // 2.3.3
                try{
                    then = x.then; // Maybe then is a getter 2.3.3.1
                    if(typeof then === 'function'){ // 2.3.3.3
                        then.call(x, function rs(y){
                            if(thenCalledOrThrow) return;
                            thenCalledOrThrow = true;
                            return resolvePromise(mPromise, y, resolve, reject); // 2.3.3.3.1
                        }, function rj(r){
                            if(thenCalledOrThrow) return;
                            thenCalledOrThrow = true;
                            return reject(r); // 2.3.3.3.2
                        });
                    }else{
                        return resolve(x); // 2.3.3.4 
                    }
                }catch(reason){ // 2.3.3.2
                    if(thenCalledOrThrow) return;
                    thenCalledOrThrow = true;
                    return reject(reason);
                }
            }else{
                return resolve(x); // 2.3.4
            }
        }
    

    这里注释中的标记代表上文提到的PromiseA+规范中的条目
    Promise.prototype.catch(onRejected)
    相当于then(null, onRejected), 注意的是在Promise里,发生的异常不会输出到控制台,而会被存为Promise的值

    5.2 async/await

    这部分内容本来是要放在es7下讲的但是Promise和async/await结合起来会解决回调地狱的问题所以es7那块就放上文章链接,这边稍微讲下async/await就好.

    在async/await之前,我们有三种方式写异步代码

    1. 嵌套回调

    2. 以Promise为主的链式回调

    3. 使用Generators

    但是,这三种写起来都不够优雅,ES7做了优化改进,async/await应运而生,async/await相比较Promise 对象then 函数的嵌套,与Generator 执行的繁琐(需要借助co才能自动执行,否则得手动调用next() ), Async/Await 可以让你轻松写出同步风格的代码同时又拥有异步机制,更加简洁,逻辑更加清晰。

    5.2.1 async/await特点

    1. async/await更加语义化,async 是“异步”的简写,async function 用于申明一个function 是异步的; await,可以认为是async wait的简写, 用于等待一个异步方法执行完成;

    2. async/await是一个用同步思维解决异步问题的方案(等结果出来之后,代码才会继续往下执行)

    3. 可以通过多层async function 的同步写法代替传统的callback嵌套

    5.2.2 async function语法

    • 自动将常规函数转换成Promise,返回值也是一个Promise对象

    • 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数

    • 异步函数内部可以使用await

    async function name([param[, param[, ... param]]]) { statements }
    name: 函数名称。
    param:  要传递给函数的参数的名称
    statements: 函数体语句。
    返回值: 返回的Promise对象会以async function的返回值进行解析,或者以该函数抛出的异常进行回绝。
    

    5.2.3 await语法

    • await 放置在Promise调用之前,await 强制后面点代码等待,直到Promise对象resolve,得到resolve的值作为await表达式的运算结果

    • await只能在async函数内部使用,用在普通函数里就会报错

    [return_value] = await expression;
    
    expression:  一个 Promise  对象或者任何要等待的值。
    
    返回值:返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
    

    5.2.4 错误处理

    在async函数里,无论是Promise reject的数据还是逻辑报错,都会被默默吞掉,所以最好把await放入try{}catch{}中,catch能够捕捉到Promise对象rejected的数据或者抛出的异常

    function timeout(ms) {
    
      return new Promise((resolve, reject) => {
    
        setTimeout(() => {reject('error')}, ms);  //reject模拟出错,返回error
    
      });
    
    }
    
    async function asyncPrint(ms) {
    
      try {
    
         console.log('start');
    
         await timeout(ms);  //这里返回了错误
    
         console.log('end');  //所以这句代码不会被执行了
    
      } catch(err) {
    
         console.log(err); //这里捕捉到错误error
    
      }
    
    }
    
    asyncPrint(1000);
    

    如果不用try/catch的话,也可以像下面这样处理错误(因为async函数执行后返回一个promise)

    function timeout(ms) {
    
      return new Promise((resolve, reject) => {
    
        setTimeout(() => {reject('error')}, ms);  //reject模拟出错,返回error
    
      });
    
    }
    
    async function asyncPrint(ms) {
    
      console.log('start');
    
      await timeout(ms)
    
      console.log('end');  //这句代码不会被执行了
    
    }
    
    asyncPrint(1000).catch(err => {
    
        console.log(err); // 从这里捕捉到错误
    
    });
    
    

    如果你不想让错误中断后面代码的执行,可以提前截留住错误,像下面

    function timeout(ms) {
    
      return new Promise((resolve, reject) => {
    
        setTimeout(() => {
    
            reject('error')
    
        }, ms);  //reject模拟出错,返回error
    
      });
    
    }
    
    async function asyncPrint(ms) {
    
      console.log('start');
    
      await timeout(ms).catch(err => {  // 注意要用catch
    
    console.log(err) 
    
      })
    
      console.log('end');  //这句代码会被执行
    
    }
    
    asyncPrint(1000);
    

    5.2.5 使用场景

    多个await命令的异步操作,如果不存在依赖关系(后面的await不依赖前一个await返回的结果),用Promise.all()让它们同时触发

    function test1 () {
        return new Promise((resolve, reject) => {
    
            setTimeout(() => {
    
                resolve(1)
    
            }, 1000)
    
        })
    
    }
    
    function test2 () {
    
        return new Promise((resolve, reject) => {
    
            setTimeout(() => {
    
                resolve(2)
    
            }, 2000)
    
        })
    
    }
    
    async function exc1 () {
    
        console.log('exc1 start:',Date.now())
    
        let res1 = await test1();
    
        let res2 = await test2(); // 不依赖 res1 的值
    
        console.log('exc1 end:', Date.now())
    
    }
    
    async function exc2 () {
    
        console.log('exc2 start:',Date.now())
    
        let [res1, res2] = await Promise.all([test1(), test2()])
    
        console.log('exc2 end:', Date.now())
    
    }
    
    exc1();
    
    exc2();
    
    

    exc1 的两个并列await的写法,比较耗时,只有test1执行完了才会执行test2

    你可以在浏览器的Console里尝试一下,会发现exc2的用Promise.all执行更快一些

    6.箭头函数


    6.1 箭头函数基本形式

    let func = (num) => num;
    let func = () => num;
    let sum = (num1,num2) => num1 + num2;
    [1,2,3].map(x => x * x);
    

    6.2 箭头函数基本特点

    (1). 箭头函数this为父作用域的this,不是调用时的this

    箭头函数的this永远指向其父作用域,任何方法都改变不了,包括call,apply,bind。
    普通函数的this指向调用它的那个对象。

    let person = {
        name:'jike',
        init:function(){
            //为body添加一个点击事件,看看这个点击后的this属性有什么不同
            document.body.onclick = ()=>{
                alert(this.name);//?? this在浏览器默认是调用时的对象,可变的?                  
            }
        }
    }
    person.init();
    

    上例中,init是function,以person.init调用,其内部this就是person本身,而onclick回调是箭头函数,
    其内部的this,就是父作用域的this,就是person,能得到name。

    let person = {
        name:'jike',
        init:()=>{
            //为body添加一个点击事件,看看这个点击后的this属性有什么不同
            document.body.onclick = ()=>{
                alert(this.name);//?? this在浏览器默认是调用时的对象,可变的?                  
            }
        }
    }
    person.init();
    

    上例中,init为箭头函数,其内部的this为全局window,onclick的this也就是init函数的this,也是window,
    得到的this.name就为undefined。

    (2). 箭头函数不能作为构造函数,不能使用new

    //构造函数如下:
    function Person(p){
        this.name = p.name;
    }
    //如果用箭头函数作为构造函数,则如下
    var Person = (p) => {
        this.name = p.name;
    }
    

    由于this必须是对象实例,而箭头函数是没有实例的,此处的this指向别处,不能产生person实例,自相矛盾。

    (3). 箭头函数没有arguments,caller,callee

    箭头函数本身没有arguments,如果箭头函数在一个function内部,它会将外部函数的arguments拿过来使用。
    箭头函数中要想接收不定参数,应该使用rest参数...解决。

    let B = (b)=>{
      console.log(arguments);
    }
    B(2,92,32,32);   // Uncaught ReferenceError: arguments is not defined
    
    let C = (...c) => {
      console.log(c);
    }
    C(3,82,32,11323);  // [3, 82, 32, 11323]
    

    (4). 箭头函数通过call和apply调用,不会改变this指向,只会传入参数

    let obj2 = {
        a: 10,
        b: function(n) {
            let f = (n) => n + this.a;
            return f(n);
        },
        c: function(n) {
            let f = (n) => n + this.a;
            let m = {
                a: 20
            };
            return f.call(m,n);
        }
    };
    console.log(obj2.b(1));  // 11
    console.log(obj2.c(1)); // 11
    

    (5). 箭头函数没有原型属性

    var a = ()=>{
      return 1;
    }
    
    function b(){
      return 2;
    }
    
    console.log(a.prototype);  // undefined
    console.log(b.prototype);   // {constructor: ƒ}
    

    (6). 箭头函数不能作为Generator函数,不能使用yield关键字

    (7). 箭头函数返回对象时,要加一个小括号

    var func = () => ({ foo: 1 }); //正确
    var func = () => { foo: 1 };   //错误
    

    (8). 箭头函数在ES6 class中声明的方法为实例方法,不是原型方法

    //deom1
    class Super{
        sayName(){
            //do some thing here
        }
    }
    //通过Super.prototype可以访问到sayName方法,这种形式定义的方法,都是定义在prototype上
    var a = new Super()
    var b = new Super()
    a.sayName === b.sayName //true
    //所有实例化之后的对象共享prototypy上的sayName方法
    
    
    //demo2
    class Super{
        sayName =()=>{
            //do some thing here
        }
    }
    //通过Super.prototype访问不到sayName方法,该方法没有定义在prototype上
    var a = new Super()
    var b = new Super()
    a.sayName === b.sayName //false
    //实例化之后的对象各自拥有自己的sayName方法,比demo1需要更多的内存空间
    

    因此,在class中尽量少用箭头函数声明方法。

    (9). 多重箭头函数就是一个高阶函数,相当于内嵌函数

    const add = x => y => y + x;
    //相当于
    function add(x){
      return function(y){
        return y + x;
      };
    }
    

    (10). 箭头函数常见错误

    let a = {
      foo: 1,
      bar: () => console.log(this.foo)
    }
    
    a.bar()  //undefined
    

    bar函数中的this指向父作用域,而a对象没有作用域,因此this不是a,打印结果为undefined

    function A() {
      this.foo = 1
    }
    
    A.prototype.bar = () => console.log(this.foo)
    
    let a = new A()
    a.bar()  //undefined
    

    原型上使用箭头函数,this指向是其父作用域,并不是对象a,因此得不到预期结果

    7.for ...of


    在谈for...of前;我们来比较for和for...in的弊端;

    1:其中for 循环的最大缺点是需要跟踪计数器和退出条件。老生常谈就不多说了。

    2:for...in  它消除了跟踪技术器和退出条件,但是依然需要使用 index 来访问数组的值,让人感觉有点不人性化,此外,当你需要向数组中添加额外的方法(或另一个对象)时,for...in 循环会带来很大的麻烦。因为 for...in 循环循环访问所有可枚举的属性,意味着如果向数组的原型中添加任何其他属性,这些属性也会出现在循环中.

    
    Array.prototype.decimalfy = function() {
      for (let i = 0; i < this.length; i++) {
        this[i] = this[i].toFixed(2);
      }
    };
     
    const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
     
    for (const index in digits) {
      console.log(digits[index]);
    }
    
    Prints:
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function() {
     for (let i = 0; i < this.length; i++) {
      this[i] = this[i].toFixed(2);
     }
    }
    

    7.1 For...of 循环

    for...of 循环用于循环访问任何可迭代的数据类型。

    for...of 循环的编写方式和 for...in 循环的基本一样,只是将 in 替换为 of,可以忽略索引。简洁方便;

    
    const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
     
    for (const digit of digits) {
      console.log(digit);
    }
    

    7.import和export


    ES6之前已经出现了js模块加载的方案,最主要的是CommonJS和AMD规范。commonjs主要应用于服务器,实现同步加载,如nodejs。AMD规范应用于浏览器,如requirejs,为异步加载。同时还有CMD规范,为同步加载方案如seaJS。

    ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。

    ES6模块主要有两个功能:export和import

    export用于对外输出本模块(一个文件可以理解为一个模块)变量的接口

    import用于在一个模块中加载另一个含有export接口的模块。

    也就是说使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。如下图(假设a和b文件在同一目录下)

    
    // a.js
     
    var sex="boy";
    var echo=function(value){
    &emsp;&emsp;console.log(value)
    }
    export {sex,echo}  
    //通过向大括号中添加sex,echo变量并且export输出,就可以将对应变量值以sex、echo变量标识符形式暴露给其他文件而被读取到
    //不能写成export sex这样的方式,如果这样就相当于export "boy",外部文件就获取不到该文件的内部变量sex的值,因为没有对外输出变量接口,只是输出的字符串。
    
    
    // b.js
    通过import获取a.js文件的内部变量,{}括号内的变量来自于a.js文件export出的变量标识符。
    import {sex,echo} from "./a.js" 
    console.log(sex)   // boy
    echo(sex) // boy
    

    a.js文件也可以按如下export语法写,但不如上边直观,不太推荐。

    // a.js
    export var sex="boy";
    export var echo=function(value){
    &emsp;&emsp;console.log(value)
    }
     
    //因为function echo(){}等价于 var echo=function(){}所以也可以写成
    export function echo(value){
    &emsp;&emsp;&emsp;console.log(value)
    }
    

    以上是export与module的基本用法,再进行拓展学习

    前面的例子可以看出,b.js使用import命令的时候,用户需要知道a.js所暴露出的变量标识符,否则无法加载。可以使用export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名。

    //a.js
    var sex="boy";
    export default sex(sex不能加大括号)
    //原本直接export sex外部是无法识别的,加上default就可以了.但是一个文件内最多只能有一个export default。
    其实此处相当于为sex变量值"boy"起了一个系统默认的变量名default,自然default只能有一个值,所以一个文件内不能有多个export default。
    
    // b.js
    本质上,a.js文件的export default输出一个叫做default的变量,然后系统允许你为它取任意名字。所以可以为import的模块起任何变量名,且不需要用大括号包含
    import any from "./a.js"
    import any12 from "./a.js" 
    console.log(any,any12)   // boy,boy
    

    8. Math对象的拓展


    Math.trunc() - 取整,去掉一个数的小数部分

    console.log(Math.trunc(3.5)); // 3
        console.log(Math.trunc(-3.5)); // -3
        // 对于非数值,Math.trunc() 内部使用Number()方法先将其转化为数值
        console.log(Math.trunc('123.456')); //123
        console.log(Math.trunc(NaN)); // NaN
        console.log(Math.trunc('abc')); // NaN
        console.log(Math.trunc()); // NaN
    

    Math.sign()

    // Math.sign() // 用来判断一个数是正数还是负数 0,对于非数值,先转化,后判断
        /*
        * 参数为正数 返回+1
        * 参数为负数,返回-1
        * 参数为0,返回0
        * 参数为-0,返回-0
        * 参数为NaN,返回NaN
        * */
    

    Math.cbrt()

    // 计算立方根
        console.log(Math.cbrt(-8)); // -2
    

    Math.hypot()

    // 返回所有参数的平方和的平凡根
        console.log(Math.hypot(3,4,5)); // 7.0710678118654755
    

    三. es7

    1. includes()方法


    Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。

    [1, 2, 3].includes(2)  // true
    [1, 2, 3].includes(4) // true
    [1, 2, NaN].includes(NaN) // true
    

    该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4, 但数组长度为3),则会重置为0开始。

    [1, 2, 3].includes(3, 3); // false
    [1, 2, 3].includes(3, -1); // true
    

    没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。

    if (arr.indexOf(el) !== -1) {
    	// ...
    }
    

    indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符进行判断,这会导致对NaN的误判。

    [NaN].indexOf(NaN) // -1
    

    includes使用的是不一样的判断算法,就没有这个问题。

    NaN].includes(NaN) // true
    

    下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。

    const contains = (() => Array.prototype.includes 
    ? (arr, value) => arr.includes(value)
    :(arr, value) => arr.some(el => el === value) 
    )()
    

    另外,Map和Set数据结构有一个has方法需要注意与includes区分。

    -Map结构的has方法,是用来查找键名的,比如Map.prototype.has(key), WeakMap.prototype.has(key), Reflect.has(target, propertyKey)

    -Set结构的has方法,是用来查找值的,比如Set.prototype.has(value), WeakSet.prototype.has(value)

    2. async\await


    这里贴一下解决地狱回调方法的链接.

    Promise,async/await解决回调地狱: www.cnblogs.com/fanghl/p/94…

    2. 求幂运算符


    基本用法:

    let a = 3 ** 2 ; // 9
    // 等效于
    Math.pow(3, 2);  // 9
    

    **是一个运算符,也可以满足类似假发的操作,如下:

    let a = 3;
    a **= 2;    // 9
    

    四.参考文章

    【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理): juejin.cn/post/684490…

    ES6、ES7特性回顾(为自己之前的周马观花买单,面试有问哦): blog.csdn.net/zww19847743…

    ES6、ES7新特性: my.oschina.net/kimyeongnam…

    es6 对象深拷贝和浅拷贝: blog.csdn.net/Dong8508/ar…

    ES6 Set进行数组去重: blog.csdn.net/qq_33350717…

    Promise 简介和使用: www.jianshu.com/p/113d426e8…

    浅谈async/await: www.jianshu.com/p/1e75bd387…

    ES6箭头函数总结: www.cnblogs.com/mengff/p/96…

    ES6:循环 for ...of..: blog.csdn.net/daikunfa/ar…

    ES6模块的import和export用法总结: blog.csdn.net/qq_20069429…

    ES6 之 Math对象的扩展: www.cnblogs.com/houfee/p/10…

    Promise,async/await解决回调地狱: www.cnblogs.com/fanghl/p/94…

    五.结语

    虽然这篇文章,大部分都是借鉴别人的文章内容,但是大部分都是我在看过之后觉得不错的才放到上面讲的,虽然干货比较少,自己总结的东西比较少吧,但是大家还是可以看看其推荐的文章,这样应该能理解更深刻,在这里还是请各位大大在评论区批评指正,最后大概下次会写与vue相关的面试题之类的,尽请期待.