ECMAScript 新特性

429 阅读18分钟

ECMAScript 的概述

  • ECMAScript 是一本脚本语言,缩写为ES,通常看做为JAVAScript 的标准化规范,实际是 JAVAScript 是ECMAScript的扩展语言,因为在ECMAScript 当中只是提供了最基本的语法(就是约定了我们的代码该如何编写,例如:如何定义变量和函数...)只是停留在语言层面,并不能完成页面当中的实际功能开发。我们经常使用的JAVAScript它实现了ECMAScript语言的标准,并且在这个基础之上做了扩展,使得我们可以在浏览器上操作DOM,BOM,在node环境可以去做读写文件之类的一些操作。
  • 总的来说在浏览器环境当中的JAVAScript,它就等于 ECMAScript + web提供的API(BOM,DOM )

1.png

  • 在node环境当中所使用的JAVAScript = ECMAScript + node提供的API

2.png

  • JAVScript 中语言本身就是 ECMAScript。随着web这种模式深入的发展,从2015年开始 ES 保持着每年一个版本的迭代,很多新特性陆续出现。导致JAVAScript 这门语言的本身也就变得越来越高级,越来越便捷。

3.png

ES2015 概述

  1. 解决原有语法上的一些问题或者不足。例如:let和const提供的块级作用域。
  2. 对原有语法进行增强,使之变得更为便捷易用。例如:解构,展开,参数默认值,模板字符串...
  3. 全新的方法,全新的功能,全新的对象。例如:promise,proxy,Object.assign()。
  4. 全新的数据类型和数据解构。例如:Symbol,set,map...

ES2015与let块级作用域

  • 在ES2015之前,ES中只有两种作用域 (全局作用域,函数作用域)
    • 全局作用域
    • 函数作用域
    • 块级作用域 (ES2015新增)
  • 以前块是没有单独的作用域的,在块中定义的成员,外部是可以访问到的。 例如:
    if(true){
        var foo = 123
    }
    console.log(foo)//123 
    //对于复杂代码是非常不利的也是不安全的
  • 使用es2015新增的let
    if(true){
        let foo = 123
    }
    console.log(foo)//foo is not defined
    //在块级作用域内定义的成员,外部是无法访问的。
    

const

  • 用来声明一个只读的常量(恒量),特点是在let的基础上多了一个只读的特性。

数组的结构

  • 数组的结构是根据位置,因为数据中的元素有下标,它是有规则的。
    const arr = [100,200,300,4]
    const [foo,bar,baz] = arr;
    console.log(foo,bar,baz)//100 200 300
    //-----------------------------------------
    const [foo,...reset] = arr;
    console.log(reset)// [ 200, 300, 4 ]
    // reset 从当前位置往后的所有成员,并把他们放到一个数组中。注意:只能在我们解构的最后一个位置用。
    //如果解构的个数小于被解构的数组长度,会按照从前到后的位置被提取。
    const arr = [100,200,300,4]
    const [foo] = arr;
    console.log(foo)//100
    //如果结构的个数大于被解构的数组长度,会显示undefined
    const [foo,bar,baz,boo,coo] = arr;
    console.log(coo)//undefined

对象的结构

    const obj = { name: "ykk", age:20 }
    const { name, phone = "123000"} = obj
    console.log(phone)//123000  添加默认值
    const obj = { name: "ykk", age:20 }
    let name = "kee"
    const { name: objName } = obj
    console.log(objName)//ykk  如果对象的结构命名和外面的命名发生冲突,我们可以给他重命名 例如 objName
    const obj = { name: "ykk", age:20 }
    let name = "kee"
    const { name: objName = "default value"} = obj
    console.log(objName)//ykk  还可以给重命名添加默认值 

模板字符串

    //传统的字符串并不支持换行,如果我们的字符串有换行符我们需要用\n字符来表示。
    //在最新的模板字符串中,他可以支持多行字符串可以在模板中直接进行换行
    const str = `hello es2015 
    this is a string`
    console.log(str)
    //通过插值表达式来嵌入字符串所对应的数值,不仅可以嵌入变量,还可以嵌入任何标准的js语句
    const name = "tom"
    const str = `HEY ${name}---${1+1} ----${true?0:1} --- ${Math.random()}`
    console.log(str)//HEY tom---2 ----0 --- 0.22368499859594526
  • 带标签的模板字符串
    //标签实际上是一个特殊的函数,添加这个标签就是调用这个函数
    const str = console.log`hello word`//[ 'hello word' ]

    const name = "tom"
    const gender = true
    function myTagFunc(strings,name,gender){
    // console.log(strings)//[ 'HEY, ', ' IS A ', '.' ] 按照表达式分割过后静态的内容,所以他是一个数组
    // console.log(name,gender)//tom true
    const sex = gender? "man" : "woman"
    return strings[0] + name +strings[1]+ sex + strings[2]
    }

    const result = myTagFunc`HEY, ${name} IS A ${gender}.`
    console.log(result)
    //标签的函数还可以接受到所有这个模板字符串表达式的返回值,比如:name gender

ES2015 字符串的扩展方法

    //字符串的扩展方法
    const message = "Error: foo is not defined"
    console.log(
        // message.startsWith("Error")//是否以Error开头
        // message.endsWith("defined")//是否以defined结尾
        // message.includes("foo")//是否包含foo
    )

参数默认值

    //函数参数的默认值
    // function foo (enable){
    // //   enable = enable || true
    //     enable = enable === undefined ? true : enable
    //   console.log(enable)
    // }
    // foo(false)
    function foo (enable = true){
        console.log(enable)//false
    }
    foo(false)

剩余数组

    // function foo(){
    //    console.log(arguments)
    // }
    // foo(1,2,3,4)
    function foo(num,...args){
        //必须要放在最后一位,并且只能使用一次
        console.log(args) //[ 2, 3, 4 ]
    }
    foo(1,2,3,4)

展开数组

    const arr = [1,2,3]
    console.log(...arr)

箭头函数

  • 箭头函数可以使我们的代码更简短,易读

对象的字面量

    //对象字面量
    const bar = 1
    const obj = {
        bar,
        method1(){
            console.log("11")
        },
        [bar]:123,
        [Math.random()]:456
    }
    console.log(obj)
    obj.method1()

对象的扩展方法

Object.assign() 将多个源对象的属性赋值到一个目标对象

    const source1 = {
        a:123,
        b:456
    }
    const target = {
        a:999,
        b:888
    }
    const source2 = {
        a:1000,
        c:666
    }
    const result = Object.assign(target,source1,source2)//{ a: 1000, b: 456, c: 666 }
    console.log(result === target)//true
    //如果源对象和目标对象有相同的属性,源对象会把目标对象覆盖,
    function func(obj){
        // obj.name = "ykk"
        //使用Object.assign()给他赋值给一个新的目标对象,这样就不会影响外面定义的obj对象了
        const result = Object.assign({},obj)
        result.name = "ykk"
        result.aa = "123"
    }
    const obj = { name: "uuu" }
    func(obj)
    console.log(obj)

Object.is() 用来判断两个值是否相等

  • 两个等号的运算符,会在比较之前自动转换数据类型
    console.log(
        0==false,//true
        0===false,//false
        NaN===NaN,//false
        +0 === -0,//true
        Object.is(+0 , -0),//false
        Object.is(NaN , NaN)//true
    )

Proxy

  • 如果我们想要监视某个对象的读写,可以使用es5提供的Object.defineProperty。
    const person = {
        name:"uu",
        age:30
    }

    const personProxy = new Proxy(person,{
        get(target,property){
        return  property in target ? target[property] : undefined
        },
        set(target,property,value){
            target[property] = value
        }
    })
    console.log(personProxy.xx)
    console.log(personProxy.age)

Proxy VS Object.defineProperty()

  • Object.defineProperty()只能够监视属性的读写
  • Proxy优点
    1. Proxy 能够监视更多对象的操作,例如delete操作,对对象当中方法的调用
        const person = {
            name:"uu",
            age:30
        }
        const personProxy = new Proxy(person,{
            deleteProperxy(target,property){
                console.log(target,property)
            delete target[property]
            }
        })
    
        delete person.name
        console.log(person)//{ age: 30 }
    
    1. Proxy 更好的支持数组对象的监视
        const list = []
        const result = new Proxy(list,{
            set (target,property,value){
            console.log('set',property,value)
            target[property] = value
            return true
            }
        })
        result.push(100)
    
    1. proxy是以非侵入的方式监管了对象的读写。一个定义好的对象不需要对对象做任何操作,就可以监视到他内部的读写。Object.defineProperty()就需要用特定的方式,单独去定义对象当中那些需要被监视的属性,对于一个已经存在的对象,需要对它做很多额外的操作。

4.png

Reflect 统一的对象操作API

  • reflect 是一个静态类,不能够通过 new Reflect().
  • Reflect 封装了一系列对对象的底层操作
  • Reflect 成员方法就是 Proxy 处理对象的默认实现
    const person = {
        name:"ykk",
        age:20
    }

    const p = new Proxy(person,{
        get(target,property){
        console.log("wtach log")
        return Reflect.get(target,property)
        }
    })
    console.log(p.name)
  • Reflect对象最大的意义是他提供了统一一套用于操作对象的API。因为之前操作对象的时候,有可能会使用Object的方法,也有 in 或者 delete 这样的操作符,对于一些新手来说太乱了,并没有什么规律。Reflect对象就很好的解决了这个问题,统一了对象的操作方式
    const obj = {
        name:"23",
        age:12
    }
    // console.log("name" in obj) 是否存在某个属性
    // delete  obj["name"] 删除对象的某个属性
    // console.log(obj)
    // console.log(Object.keys(obj)) 获取对象的所有属性

    console.log(Reflect.has(obj,'name'))
    console.log(Reflect.deleteProperty (obj,'age'))
    console.log(Reflect.ownKeys(obj))

Promise

  • 一种全新的异步编程解决方案,通过链式调用方式,解决了传统异步编程中回调函数嵌套过深的问题。

class

    // function Person(name){
    //     this.name = name
    // }

    // Person.prototype.say = function (){
    //      console.log(`hi my name is ${this.name}`)
    // }

    // let p = new Person("tom")

    // p.say()

    class Person{
        constructor(name){
        this.name = name
        }

        say(){
            console.log(`hi my name is ${this.name}`)
        }
    }

    let p = new Person("jack")
    p.say()
  • 这种独立定义的语法,相比较之前函数的方式更容易理解,结构更加清晰

ES2015 静态方法

    // static 方法
    class Person {
        constructor(name){
            this.name = name
        }

        say(){
            console.log(this.name)
        }
        // 给当前类型 person 去添加一个create 静态方法,用于创建 person类型的实例
        static create(name){
            return new Person(name)
        }
    }

    let p =Person.create("tom")
    p.say()
  • 注意:因为静态方法是挂载到类型上面的,所以在静态方法内部他的this就不会指向某一个实例对象,而是当前的类型

ES2015 类的继承

  • 在es2015之前使用继承大多都是用原型的方式去实现继承。在2015中实现了一个专门实现类型继承的关键词 extends。
  • extends 相比于原型继承更方便,更清楚
    class Person{
    constructor(name){
        this.name = name
    }

    say(){
        console.log("person",this.name)
    }
    }
    //Student 继承 Person。Student类型当中就会有Person所有的成员了
    class Student extends Person{
        constructor(name,number){
            //super对象始终指向父类,调用它就像调用父类的构造函数
            super(name)
            this.number = number
        }

        hello(){
            super.say()
            console.log("Student",this.number)
        }
    }

    let student = new Student("jack",2)
    student.hello()

ES2015 Set

  • set数据结构,可以理解为集合,与传统的数组比较相似,不过set内部的成员是不允许重复的,每一个值在同一个set中都是唯一的。
    //set 数据结构

    let s = new Set()
    //add方法添加一些数据,因为这个方法返回集合本身,所以可以链式调用
    s.add(1).add(2).add(3).add(4).add(5)

    //通过size属性获取整合集合的长度,与数组中的length相同
    // console.log(s.size)

    // s.forEach(i=>console.log(i))
    // for(let val of s){
    //     console.log(val)
    // }
    console.log(s.has(1))//是否存在某一个特定的值
    console.log(s.delete(1))//删除某一个值
    // s.clear()//清楚当前集合当中的全部内容
    // console.log(s)

    //数组去重

    let arr = [1,2,3,4,2,4,5]
    // let result = Array.from(new Set(arr))
    let result = [...new Set(arr)]

    console.log(result)

ES2015 Map

    //map 数据结构

    // let obj = {}
    // obj[true] = "value"
    // obj[123] = 1
    // obj[{a:1}] = "value"
    // obj['[object Object ]'] = "value"
    // console.log(obj)
    // {
    //     true: 'value',
    //     '[object Object]': 'value',
    //     '[object Object ]': 'value'
    // }
    // console.log(Object.keys(obj))//[ '123', 'true', '[object Object]', '[object Object ]' ]
    //对象的建只要不是字符串类型都会转化为字符串(进行toString),每一个对象toString的结构默认都是一样的,并不能进行区分。为了解决这个问题,就有了map这样的数据结构,
    //map数据结构才是严格意义上的键值对集合,用来映射两个任意类型集合对应的关系。

    let map = new Map ()
    let tom = { name: 'jack'}
    map.set(tom,20)
    map.set(true,1).set("num","123")
    // console.log(map.has(tom))
    // console.log(map.delete(tom))
    // map.clear()
    map.forEach((value,key) =>{
        console.log(value,key)
    })
  • map与对象最大的不同就是他可以用任意类型的数据作为键,而对象实际上只能使用字符串作为键

ES2015 Symbol

  • 在ES2015之前对象的属性名都是字符串,而字符串有可能就会有重复的,重复的话就会产生冲突。
  • 通过Symbol创建的每一个值都是唯一的,他永远不会重复
    // const s = Symbol()
    // console.log(s)
    // console.log(typeof s)//symbol
    // 通过typeof 打印出来symbol,表示他确实是一种全新的数据类型。

    // console.log(
    //     Symbol() === Symbol() false
    // )
    //Symbol 类型最大的特点就是独一无二的,通过他创建的类型永远都不会重复

    // console.log(Symbol("foo"))
    // console.log(Symbol("bar"))
    // console.log(Symbol("baz"))

    // Symbol 可以传入一个字符串,作为这个值得描述文本,对于多次使用Symbol的情况,便于我们区分到底是那个对应的Symbol

    //从ES2015开始,对象就可以使用Symbol类型的值作为属性名,现在对象的属性名可以是两种类型,一种是字符串,一种是Symbol

    // const obj = {}
    // obj[Symbol()] = 123
    // obj[Symbol()] = 456
    // console.log(obj)
    //因为Symbol的值都是独一无二的,所以就不用担心可能会产生冲突的问题。

    //使用计算属性名的方式,在对象字面量当中去使用Symbol作为对象的属性名
    // const obj = {
    //     [Symbol()] :123
    // }
    // console.log(obj)

    //Symbol除了可以避免对象属性名重复产生的问题,我们还可以借助Symbol这种类型特点,去模拟对象的私有成员。
    //a.js==============================
    const name = Symbol()
    const person = {
        [name]:123,
        say(){
            console.log(this[name])
        }
    }
    //b.js==============================
    // console.log(person[name])//因为我们没有办法在创建一个完全相同的Symbol,所以我们就不能去访问到这个成员,只能去调用这个成员当中普通名称的成员
    person.say()
    //Symbol最主要的的作用就是为对象添加独一无二的属性名

ES2015 Symbol 补充

    // console.log(
    //     Symbol() === Symbol(), //false
    //     Symbol("foo") === Symbol("foo")//false
    // )
    //通过Symbol函数创建的值都是唯一,不管我们的传入的描述文本是不是相同的,每次调用Symbol函数得到的结构都是全新的一个值。
    //假如我们想在全局去复用一个相同的Symbol值,我们可以使用全局变量的方式去实现或者使用Symbol类型提供的静态方法去实现。

    // for方法可以接受一个字符串作为参数,相同的字符串返回相同的Symbol类型的值。这个方法内部维护了一个全局的注册表,为字符串和Symbol值提供了一个一一对应的关系
    // const s1 =  Symbol.for("foo")
    // const s2 =  Symbol.for("foo")
    // console.log(
    //     s1 === s2 //true
    // )

    //注意: for方法内部维护的是字符串和Symbol对应的关系,如果我们传入的不是字符串,这个方法内部会把它转化为字符串。这样就会导致我们传入布尔值的true和字符串的 "true"结果拿到的是一样的。

    // console.log(
    //     Symbol.for(true) === Symbol.for("true")//true
    // )

    //在Symbol类型当中还提供了很多内置的Symbol常量,用来作为内部方法的标识。这些标识符可以让自定义对象实现一些js当中内置的接口
    // console.log(Symbol.iterator)
    // console.log(Symbol.hasInstance)

    //自定义对象的toString标签
    // const obj = {
    //     //如果使用字符串标签去添加标识符,有可能会和内部的成员产生重复,ECMAScript要求我们使用Symbol去实现这个的一个接口
    //     [Symbol.toStringTag]:"xObject"
    // }
    // console.log(obj.toString())//[object xObject] 

    const obj = {
        [Symbol()]:"symbol value",
        foo:123
    }
    for(let val in obj){
        console.log(val) //foo
    }
    console.log(Object.keys(obj))//[ 'foo' ]
    console.log(JSON.stringify(obj))//{"foo":123}
    //使用Symbol值作为对象的属性名,通过传统的for in 循环和Object.keys是无法拿到的。通过JSON.stringify去序列化对象为一个JSON字符串Symbol属性也是会忽略掉。
    //通过以上三种方法都无法获取Symbol属性名,这些特性使得Symbol类型的属性特别适合作为对象的私有属性。

    //如果想获取对象的Symbol属性也不是没有办法,我们可以使用Object.getOwnPropertySymbols方法,类似于对象的Object.keys()方法。
    //Object.keys()方法只能够获取到对象中所有的字符串属性名
    //Object.getOwnPropertySymbols()获取的是Symbol类型的属性名
    console.log(Object.getOwnPropertySymbols(obj))//[ Symbol() ]

for of 循环

  • 在ECMAScript当中遍历数据有很多种方法,最基本的for循环,比较适用于遍历普通的数组,然后还有for in 循环 比较适合遍历键值对,还有一些函数式的遍历方法... 例如数组对象的forEach方法。各种各样的遍历数据方式都有一定的局限性。所以es2015借鉴了很多其他的语言,引入了一种全新的遍历方式 for of 循环。
  • for of 循环作为遍历所有数据结构的统一方式。
    const arr = [ 100, 200, 300, 400 ]

    // for(let item of arr){
    //     console.log(item)
    // }
    //for of 循环拿到的是数组的每一个元素,而不是对应的下标。

    for(let item of arr){
        console.log(item)
        if(item>100){
            break
        }
    }
    //for of 可以跳转循环,随时终止遍历
    // arr.forEach()//不能跳出循环
    // arr.some()返回true可以跳出循环
    // arr.every()返回false可以跳出循环

    // const s = new Set(['foo','bar'])
    // for(const item of s){
    //     console.log(item)
    // }

    // const m = new Map()
    // m.set("foo",123).set("boo",456)
    // for(const [key,value] of m){
    //     console.log(key,value)
    // }
    const obj = { name:'kk', age: 19 }

    for(const item of obj){
        console.log(item) //obj is not iterable
    }
    //for of 循环可以作为遍历所以数据结构的统一方式,但是却连最基本的普通对象都无法遍历?为什么?

可迭代接口

  • for...of 循环是一种数据统一遍历方式,但是经过试验,发现它只能够遍历数组之类的数据结构,对于普通的对象如果直接遍历就会报出错误。
  • ES能够表示有结构的数据类型越来越多,从最早的数组,对象到现在新增了set和map。开发者还可以组合使用这些类型去定义符合自己业务需求的数据结构。为了提供一种统一的遍历方式,ES2015就提出了一种Iterable接口,可迭代的。
  • 可迭代接口就是一种可以被for...of循环统一遍历访问的规格标准。只要这个数据他实现了可迭代接口,他就能够被for..of循环去遍历。之前尝试的set map,数组可以被for...of遍历的数据,是因为他们内部都已经实现了这个接口

arr.png

map.png

set.png

const set = new Set(["foo","bar","coo"])
let interator = set[Symbol.iterator]()

console.log(interator.next())
console.log(interator.next())
console.log(interator.next())
console.log(interator.next())
console.log(interator.next())
//打印如下:
// { value: 'foo', done: false }
// { value: 'bar', done: false }
// { value: 'coo', done: false }
// { value: undefined, done: true }
// { value: undefined, done: true }

实现可迭代接口

    //实现可迭代接口

    // const obj = {
    //     //iterable
    //     [Symbol.iterator]:function(){
    //         //iterator 内部必须要有一个next方法
    //         return {
    //             next:function(){
    //                 //iterationResult 实现迭代结果的接口
    //                 //这个接口约定的是在这个对象内部必须要有一个value属性用来表示当前被迭代到的数据,值可以是任意类型。
    //                 //还需要有一个done的布尔值,用来表示迭代有没有结束
    //                 return {
    //                     value: "ykk",
    //                     done: true
    //                 }
    //             }
    //         }
    //     }
    // }
    const obj = {
        store:["foo","bar","boo"],
        [Symbol.iterator]:function(){
            let self = this
            let index = 0
            return {
                next:function(){
                    const result = {
                        value: self.store[index],
                        done: index>=self.store.length
                    }
                    index ++
                    return result
                }  
            }
        }
    }

    for(const item of obj){
        console.log(item)
    }

ES2015 迭代器模式

    //迭代器设计模式

    //场景: 两人协同共同开一个任务清单

    // 小明的代码======================
    const todos = {
        life:["逛淘宝","花钱","买衣服","吃小龙虾"],
        learn:["语文","数学","英语"],
        work:["喝茶"],
        each:function(callback){
            let arr = [].concat(this.life,this.learn,this.work)
            for(const item of arr){
                callback(item)
            }
        },
        [Symbol.iterator]:function(){
            const arr = [...this.life,...this.learn,...this.work]
            let index = 0
            return {
                next:function(){
                    const result = {
                        value:arr[index],
                        done:index++>=arr.length
                    }
                    return result
                }
            }
        }
    }

    // 小红的代码===========================

    // for(const item of todos.life){
    //     console.log(item)
    // }
    // for(const item of todos.learn){
    //     console.log(item)
    // }
    // for(const item of todos.work){
    //     console.log(item)
    // }
    //如果数据结构发生变化,添加了一个全新的类目,但是目前小红的代码和小明的代码数据结构是严重耦合的,也需要跟着一起去变化

    //数据结构可以对外提供一个统一的遍历接口

    todos.each(function(item){
        console.log(item)
    })

    for(const item of todos){
        console.log("迭代器",item)
    }
  • 迭代器他的核心意义就是对外提供统一遍历接口,外部不用去关系数据的内部结构是什么样的,ES2015迭代器实现的语言层面的迭代器模式,可以适用于任何数据结构,只需要你的代码实现iterator方法,以及他的迭代逻辑。

ES2015 生成器

  • 引入Generator生成器的目的就是为了能够在复杂的异步代码中减少回调函数嵌套产生的问题,提供更好的异步编程解决方案
    // function *foo(){
    //     console.log("log")
    //     return 100
    // }
    // const result = foo()
    // console.log(result.next())//{ value: 100, done: true }
    // next方法的返回值与迭代器方法的返回值有相同的结构,说明 generator 也实现了 iterator接口

    function * foo(){
        console.log(11)
        yield 100
        console.log(22)
        yield 200
        console.log(33)
        yield 300
    }

    const result = foo()
    console.log(result.next())
    console.log(result.next())
    console.log(result.next())
    console.log(result.next())

    // 生成器函数会自动返回生成器对象,调用这个对象的方法,函数体开始执行,执行过程中遇到 yield关键词暂停下来,yield后面的值会作为后面的结果返回,周而复始。
    // 生成器函数最大的特点就是惰性执行。
    
  • generator 应用
    // 使用generator 函数实现 iterator方法
    const todos = {
        life:["逛淘宝","花钱","买衣服","吃小龙虾"],
        learn:["语文","数学","英语"],
        work:["喝茶"],
        [Symbol.iterator]:function * (){
            const arr = [...this.life,...this.learn,...this.work]
            for (const item of arr){
                yield item
            }
        }
    }

    for (const item of todos){
        console.log(item)
    }

ES2016 概述

  • 与ES2015相比 ES2016只是一个小版本,仅包含两个小功能。
    //ES2016
    // Array.prototype.includes----------------
    const arr = ["foo", NaN, false]

    console.log(arr.indexOf("foo"))
    console.log(arr.indexOf(NaN))//false
    console.log(arr.includes(NaN))//true

    // ES2015之前我们查看数组当中是否存在某个值使用的是indexOf方法,indexOf不能查找数组当中是否存在NaN。 

    //指数运算符---------------------------------

    console.log(Math.pow(2,10))

    console.log(2 ** 10)

ES2017 概述

    const obj = {
        foo:"value1",
        bar:"value2"
    }
    // Object.values()返回对象当中所有值组成的数组----------------------------
    // console.log(Object.values(obj))
    // Object.entries()是以数组的方式返回对象的键值对-------------------------
    // console.log(Object.entries(obj))// [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]

    // for (const [key,value] of Object.entries(obj)){
    //     console.log(key,value)
    // }

    console.log(new Map(Object.entries(obj)))//Map(2) { 'foo' => 'value1', 'bar' => 'value2' }
    // Object.getOwnPropertyDescriptors----------------------------------

    // String.prototype.padEnd------------------------------------
    // String.prototype.padStart----------------------------------
    //Async/Await