zoukankan      html  css  js  c++  java
  • ES6-ES10知识

    1.环境准备

    1.1 初始化项目

    安装较高版本的node之后,输出下面命令安装项目,初始化项目,

    npx es10-cli create es6-10-project
    
    npm start    //启动项目
    

    1.2 visual studio code安装插件

    beautify 和eslint插件

    2.ES2015

    2.1 let 和const

    2.1.1 全局作用域

    直接定义了html文件,文件引入demo.js和demo1.js

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>ES6 入门导学</title>
      </head>
      <body>
        <h1>h33</h1>
        <script src="./static/demo.js" charset="utf-8"></script>
        <script src="./static/demo.1.js" charset="utf-8"></script>
      </body>
    </html>
    
    

    demo.js文件内容如下,定义了一个变量a

    a="aa";
    var b="bbb"
    

    demo.1.js文件内容如下,输出变量a的内容

    console.log(a);
    //aa
    console.log(b);
    //bbb
    

    在浏览器中运行发现,变量a和变量b的内容被输出。在控制台中打印window.a发现也是可以访问到a变量的,说明a是个全局变量,直接打印a也是可以访问到的。

    >a
    "aa"
    >b
    "bbb"
    >window.a
    "aa"
    >window.b
    "bbb"
    >delete a
    true
    //没有被var定义的对象,作为全局对象window的一个属性,可以被删除
    >delete window.a
    true
    //没有被var定义的对象,作为全局对象window的一个属性,可以被全局访问,可以被删除
    >delete b
    false  
    //被var定义的对象,作为全局变量(对象),不能被删除,
    

    全局变量具有全局作用域,在函数内部或者代码块中没有被var定义的变量都具有全局作用域,但不是全局变量。

    这2个看上去一样,其实不一样。其实第1个不能被称为全局变量,根据delete的结果来判断(没有被var定义的变量不支持,不叫全局变量),delete可以删除对象的属性。

    window对象的属性,可以不加window.,也可以被访问到。

    全局变量是不可以被删除的,对象中的属性是可以被删除的。

    window对象是一个全局对象,var声明的变量其实和window对象一样,没有被var声明的对象被当成全局对象的属性在用。

    接下来看一个在函数作用域内中定义的变量的区别,修改demo.js文件,文件内容如下:

    a="aa";
    var b="bbb"
    function test() {
        var abc="abc"
        ab=45
    }
    test()
    

    修改demo.1.js文件,文件内容如下:

    console.log(a);
    console.log(b);
    
    console.log(ab);
    //45
    console.log(abc);
    //demo.1.js:5 Uncaught ReferenceError: abc is not defined
    

    在函数内部或者代码块中没有被var定义的变量都是全局变量的属性,具有全局作用域。**

    2.1.2 let 和const

    比较let和var的区别,分析下面代码;

    var b=3
    let c=4
    console.log(b,c)
    //3 4
    console.log(window.b,window.c)
    //3 undefined
    

    let 不能重复定义,不能进行变量提升,不能作为全局变量的属性,具有块级作用域。

    let有的特性,const 都有,而且const定义的变量不能被修改,const一定要在初始化的时候被赋值。

    2.2 Array

    数组中主要包含遍历 、转换、生成、查找操作,下面介绍ES5和ES6主要包含的数组中关于这些操作的方法

    2.2.1 数组的遍历

    ES5中数组遍历有哪些方法?有什么优势和缺点

    //ES5数组遍历
    const arr=[1,2,3,4,5]
    
    //for循环,比较麻烦
    for(let i=0;i<arr.length;i++) {
        
        if(arr[i]==22) {
            continue
        }
        console.log(arr[i])
    }
    //1 2 3 4 5
    
    //forEach,比较简单;但是不支持continue和break
    arr.forEach(function(item){
        console.log(item)
        if(item===2) {
           // continue //不支持,会报错
           // break  //不支持,会报错
        }
    })
    // 1 2 3 4 5
    
    
    //every
    arr.every(function(item){
        console.log(item)
    })
    //1 
    //默认返回未false,不往后遍历
    arr.every(function(item){
        console.log(item)
        if(item===2) {
            return false
        }
        return true
    })
    // 1 2 3 4 5
    
    arr.every(function(item){
        if(item===2) {
            return false
        }
        console.log(item)
        return true
    })
    //1 
    //every实现continue
    arr.every(function(item){
        if(item===2) {
        }else {
            console.log(item)
        }    
        return true
    })
    //1 3 4 5
    
    
    //forin 为object遍历设计,不是为数组设计的,数组也是对象
    for (let index in arr) {
        console.log(index,arr[index])  
    }
    
    // 0 1
    // 1 2
    // 2 3
    // 3 4
    // 4 5
    
    //forin实现continue效果
    
    for (let index in arr) {
        if(index==2) {
            continue
        }
        console.log(index,arr[index])  
    }
    
    // 0 1
    // 1 2
    // 3 4
    // 4 5
    
    //forin不能实现continue效果,index是字符串,(===)就不会相等了,
    //因为会检查类型,在控制台是黑色的,黑色代表字符串,蓝色代表数字
    for (let index in arr) {
        if(index===2) {
            continue
        }
        console.log(index,arr[index])  
    }
    
    // 0 1
    // 1 2
    // 1 2
    // 3 4
    // 4 5
    
    //forin里面的index,这个字符串可以转换为数组,通过index*1
    for (let index in arr) {
        if(index*1===2) {
            continue
        }
        console.log(index,arr[index])  
    }
    // 0 1
    // 1 2
    // 3 4
    // 4 5
    
    
    
    //forin会把数组的属性值也会遍历出来
    arr.a=8
    for (let index in arr) {
        console.log(index,arr[index])  
    }
    // 0 1
    // 1 2
    // 2 3
    // 3 4
    // 4 5
    // a 8
    

    ES6中数组的遍历-forof

    //ES6数组遍历
    //for of,ES6允许自定义数据结构,出现不是数组,不是object的对象,除了数组和object其他的遍历用for in
    for (let item of arr) {
        console.log(item)    
    }
    
    
    const price={
        A:[1.5,2.3,4.5],
        B:[3,4,5],
        C:[0.5,0.8,1.2]
    }
    for (let key in price) {
        console.log(key,price[key])
    }
    
    //  A(3) [1.5, 2.3, 4.5]
    //  B (3) [3, 4, 5]
    //  C (3) [0.5, 0.8, 1.2]
    
    

    2.2.2 Array(伪数组转换成数组)

    在特性上像数组,但是不能直接调用数组的方法

    //伪数组按照索引存储数据,有length属性

    //ES5伪数组转换成数组
    let args=[].slice.call(arguments)
    console.log(args)
    let img=[].slice.call(document.querySelectorAll('img')) //nodelist
    
    //伪数组按照索引存储数据,有length属性
    //ES6伪数组转换成数组
    let args=Array.from(arguments)
    let img=Array.from(document.querySelectorAll('img'))ES5
    

    声明一个长度为5的数组,把数组中的元素初始化为1,ES5和ES6的做法如下,Array.from的方法代码要简洁很多

    //把数组中的元素初始化为1
    //ES5的做法
    let array=Array(5)
    for(let i=0;i<5;i++) {
        array[i]=1
    }
    console.log(array)
    //(5) [1, 1, 1, 1, 1]
    
    
    //ES6的做法,把数组中的元素初始化为1
    let array2=Array.from({length:5},function() {
        return 1
    })
    console.log(array2)
    // (5) [1, 1, 1, 1, 1] 
    

    2.2.3 创建一个新数组

    ES5和ES6创建数组的方法有哪些呢?

    //生成新数组
    //ES5
    let array=Array(5)
    let arry2=["",""]
    arry2.push('a')
    
    //ES6,允许把多个元素放到一个数组,ES5可以使用push的方法
    //Array.of可以生成有一个或者多个元素的数组
    let array3=Array.of(1,2,3,4,5)
    console.log(array3)
    //(5) [1, 2, 3, 4, 5]
    
    //生成一个数组,把数组中的元素初始化为1,更简单的方法是用fill
    let arrayFill=Array(5).fill(1)
    console.log(arrayFill)
    //(5) [1, 1, 1, 1, 1]
    
    
    //如何替换数组的某一段的值呢?这里可以用fill函数指定要替换的位置范围
    arrayFill.fill(8,2,4)
    console.log(arrayFill)
    //(5) [1, 1, 8, 8, 1]
    

    2.2.4 数组里查找元素

    ES5和ES6如何查找元素

    //ES5如何查找一个元素呢,通过filter返回一个新数组,通过判断返回的数组长度是否为0 来判断是否存在该元素
    let array=[1,2,3,4,5]
    let find=array.filter(function(item){
        return item=== 3
    })
    console.log(find)
    //[3]
    
    find=array.filter(function(item){
        return item%2===0
    })
    //(2) [2, 4]
    console.log(find)
    
    //fiter会返回满足条件的所有值
    
    //ES6中使用find和findIndex来查找元素,find只返回满足条件的第1个值
    //findIndex会返回满足条件的索引值
    find=array.find(function(item){
        return item===3
    })
    console.log(find)
    //3
    find=array.find(function(item){
        return item===6
    })
    console.log(find)
    //undefined
    
    find=array.find(function(item){
        return item%2===0
    })
    console.log(find)
    //2
    
    
    //findIndex会返回满足条件的索引值
    let findIndex=array.findIndex(function(item){
        return item===3
    })
    console.log(findIndex)
    

    总结:数组中主要包含遍历 、转换、生成、查找操作,ES5和ES6主要包含,下次自己写代码时候,要注意使用数组提供的这些方法。

    2.3 类声明

    2.4 函数和数据结构

    2.4.1 如何处理函数的默认值

    //参数默认值,可选参数和必选参数,x是必选,y和z是可选参数
    //ES5的函数参数缺省默认值
    function f(x,y,z) {
        if(y===undefined) {
            y=7
        }
        if(z==undefined) {
            z=42
        }
        return x+y+z
    }
    console.log(f(1))
    // 50
    console.log(f(1,8))
    //51
    
    //ES6的函数参数缺省默认值
    function f2(x,y=7,z=42){
        return x+y+z
    }
    console.log(f2(1))
    // 50
    console.log(f2(1,8))
    //51
    
    //如果不想传递y,传递z呢,通过undefined来跳过
    console.log(f2(1,undefined,43))
    //51
    
    //参数默认值还可以其他参数的表达式(z=x+y)
    function f3(x,y=7,z=x+y){
        //arguments是一个伪数组
        console.log(Array.from(arguments))
        // [1, undefined, 2]
        //E5中arguments表示当前函数的参数情况,ES6不让使用arguments
        return x*10+z
    }
    console.log(f3(1,undefined,2))
    
    //ES6如何判断函数参数的个数,用函数体的length属性可以获取没有 默认值参数的个数
    function f4(x,y=7,z=x+y){
        console.log(f4.length)
        //1
        return x*10+z
    }
    
    console.log(f4(1,undefined,2))
    

    2.4.2 如何处理不确定参数的问题

    //求和:函数中所有参数的和
    //如何获取函数执行时参数的值呢
    function sum() {
        let num=0
        //ES5中利用arguments获取所有的函数参数
        Array.from(arguments).forEach(function(item){
            num+=item*1
        })
        return num
    }
    
    //ES6
    function sum2(...nums) {
        //rest 参数不确定时,放到nums里面
        let num=0
        nums.forEach(function(item){
            num+=item*1
        })
        return num
    }
    console.log(sum2(1,2,3))
    //6
    
    //base可以取第1个参数,nums取后面的参数
    function sum3(base,...nums) {
        //rest 
        let num=0
        nums.forEach(function(item){
            num+=item*1
        })
        return base*2+num
    }
    console.log(sum3(1,2,3))
    //7
    
    
    

    当收敛函数参数的过程反过来时候,如下代码所示

    //...和...sum的区别,是反操作,一个是散(spread),一个是收(restful)
    //...是将数组打散,而restful是将x,y,z不同的参数收敛到数组中

    //当这个过程反过来时
    //计算三角形周长
    function sum(x=1,y=2,z=3){
        return x+y+z
    }
    
    //第一种方式,有点土
    let data=[4,5,6]
    console.log(sum(data[0],data[1],data[2]))
    //15
    
    //上面的方式有点土,通过apply方式也可以
    //ES5通过apply的特性,接受参数
    console.log(sum.apply(this,data))
    //15
    
    //ES6中通过...的方式将数组中的数字分给函数参数
    console.log(sum(...data))
    //15
    
    //...和...sum的区别,是反操作,一个是散(spread),一个是收(restful)
    //...是将数组打散,而restful是将x,y,z不同的参数收敛到数组中
    
    

    2.4.3 箭头函数

    ()=>{}这样的形式,()放参数,{}放函数体

    //ES5定义函数
    // function hello() {}
    // let hello=function() {
        
    // }
    
    //ES6箭头函数
    let hello=()=>{
        console.log('hello world')
    }
    hello()
    //hello world
    
    //箭头函数传递参数
    let hello1=(name)=>{
        console.log('hello world',name)
    }
    hello1('immoc')
    
    let hello2=(name,city)=>{
        console.log('hello world',name,city)
    }
    hello2('immoc','beijing')
    //hello world immoc beijing
    
    

    关于()的省略问题

    //箭头函数没有参数的时候,()不可以被省略;
    //有且只有一个参数的时候,()可以被省略
    //多于一个参数时,()不可以被省略
    let hello3=name=>{
        console.log('hello world',name)
    }
    hello1('immoc')
    
    

    关于{}的省略问题

    //如果函数返回是表达式,{}可以被省略
    //如果函数返回一个对象,对象要用()括起来
    //其他情况,{}不可以被省略
    
    //箭头后边是表达式
    let sum=(x,y,z)=>x+y+z
    console.log(sum(1,2,3))
    //6
    
    //返回一个对象,()被当作运算表达式,{}被当作对象
    let sum1=(x,y,z)=>({
        x:x,
        y:y,
        z:z
    })
    console.log(sum1(1,2,3))
    //Object {x: 1, y: 2, z: 3}
    //记得不清楚的话,可以老老实实地写
    let sum2=(x,y,z)=>{
        return {
            x:x,
            y:y,
            z:z
        }    
    }
    console.log(sum2(1,2,4))
    //Object {x: 1, y: 2, z: 4}
    
    

    箭头函数关于this指向问题的处理

    //箭头函数关于this指向问题的处理
    let test={
        name:'test',
        say:function() {
            console.log(this.name)
        }
    }
    test.say()
    //test
    
    //为什么这里会输出test呢?
    //记住一句话,关于this的指向,是谁在调用这个function,this指向这个谁,指向调用者
    //say()函数被test调用,所以this指向test
    
    //把上面普通函数的改成箭头函数
    let test2={
        name:'test',
        say:() => { 
            console.log(this)    
            console.log(this.name)
        }
    }
    test2.say()
    //Object {}
    //undefined
    
    //当把上面代码放到控制台中执行时,发现this指向的是window
    // let test2={
    //     name:'test',
    //     say:() => { 
    //         console.log(this)    
    //         console.log(this.name)
    //     }
    // }
    // test2.say()
    // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
    // undefined
    
    
    //2者结果为什么会不同呢?是因为eval
    //eval会在浏览器中执行代码的时候,把this指向{}空对象
    
    //为什么会输出undefined呢?
    //普通函数和箭头函数关于this的指向定义不同
    //es5中this指向函数的调用者,
    //而es6中是定义的时候,this指向谁,执行的时候this指向和定义的时候一样
    //es6中,写箭头函数的时候,this指向哪里,执行时,this就指向哪里
    
    

    2.5 object updates

    ES5中Object属性能简写吗?

    ES6可以吗?

    let obj={
        x:1,
        y:2
    }
    //如果对象属性是变量呢,es5的写法
    let x=1;let y=2
    let obj1={
        x:x,
        y:y
    }
    console.log(obj)
    //{ x: 1, y: 2 }
    
    //es6可以简写成这样,和上面是一样的
    let obj2={
        x,
        y
    }
    console.log(obj2)
    //{ x: 1, y: 2 }
    
    
    //如果想给object动态添加变量属性值呢
    //ES5的写法
    let x=1;let y=2;let z=3
    let obj={
        'x':x,
         y
    }
    obj[z]=5
    console.log(obj)
    //Object {3: 5, x: 1, y: 2}
    //z变量已经被动态添加到obj对象的属性上面了
    
    //ES6呢,如下,更方便了
    let obj2={
        'x':x,
         y,
         [z]:6
    }
    console.log(obj2)
    //Object {3: 6, x: 1, y: 2}
    
    //还可以使用表达式
    let obj3={
        'x':x,
         y,
         [z+y]:6
    }
    
    console.log(obj3)
    //Object {5: 6, x: 1, y: 2}
    
    
    //关于对象里面的函数
    //ES5
    let obj4={
        'x':x,
         say:function(){
            console.log('hello world')
        }
    }
    obj4.say()
    //hello world
    
    //ES6
    let obj5={
        'x':x,
         say(){
            console.log('hello world')
        }
    }
    obj5.say()
    //hello world
    
    //在ES5中是不允许在object中添加异步函数的,
    //在ES6中是可以添加异步函数的,通过加*号来区分
    
    let obj6={
        'x':x,
         * say(){
            console.log('hello world')
        }
    }
    obj6.say()
    //没有输出
    //异步函数为什么没有被执行输出呢呢?后面再介绍
    
    
    

    //set,不能重复相同的对象

    2.6 扩展运算符(...)

    参考博客https://www.cnblogs.com/minigrasshopper/p/9156027.html

    扩展运算(spread)是三个点(...),它好比rest参数的逆运算,将一个数组或者对象转为用逗号分隔的参数序列。

    2.6.1 基本用法

    //1.入门
    //在浏览器console窗口中找到本页面所有的div对象,返回值是nodelist对象
    document.querySelectorAll('div')
    //NodeList(3) [div#wrapper.wrapper_l, div#head, div#u]
    
    //使用扩展运算符将NodeList对象转换为数组
    [...document.querySelectorAll('div')]
    //(3) [div#wrapper.wrapper_l, div#head, div#u]
    
    
    let arr=[1,2,3]
    console.log(arr)
    //Array(3) [1, 2, 3]
    console.log(...arr)
    //1 2 3
    console.log(...[1, 2, 3]) 
    //1 2 3
    
    

    2.6.2 替代数组的apply方法和处理数组的不确定参数

    //替代数组的apply方法
    function f(x,y,z) {
        return x+y+z
    }
    let args=[0,1,2]
    
    //ES5的写法,将数组转为函数的参数
    
    console.log(f.apply(this,args))
    console.log(f.apply(null,args))
    
    //ES6的写法
    console.log(f(...args))
    
    
    //处理数组的不确定参数
    //ES5中利用arguments获取所有的函数参数
    function sum() {
        let num=0    
        Array.from(arguments).forEach(function(item){
            num+=item*1
        })
        return num
    }
    
    //ES6中,rest 参数不确定时,放到nums里面
    function sum2(...nums) {
        //rest 参数不确定时,放到nums里面
        let num=0
        nums.forEach(function(item){
            num+=item*1
        })
        return num
    }
    console.log(sum2(1,2,3))
    
    

    下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法。

    let maxN=Math.max.apply(null,[14,3,17])
    console.log(maxN)
    //17
    maxN=Math.max.apply[14,3,17]
    console.log(maxN)
    //undefined,因为max函数接收参数序列,不接收数组
    maxN=Math.max(...[14,3,17])
    console.log(maxN)
    //17
    
    

    上面代码表示,由于 JavaScript 不提供求数组最大元素的函数,所以只能套用Math.max函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用Math.max了。

    另一个例子是通过push函数,将一个数组添加到另一个数组的尾部。

    let arr1=[1,2,3]
    let arr2=[4,5,6]
    //ES5的语法,通过push函数,将一个数组添加到另一个数组的尾部
    let resultArr=Array.prototype.push.apply(arr1,arr2)
    console.log(arr1)
    //Array(6) [1, 2, 3, 4, 5, 6]
    console.log(arr2)
    //Array(3) [4, 5, 6]
    console.log(resultArr)
    //6
    
    //ES6的语法,
    arr1=[1,2,3]
    arr2=[4,5,6]
    arr1.push(...arr2)
    console.log(arr1)
    //Array(6) [1, 2, 3, 4, 5, 6]
    
    

    2.6.3 用于数组的操作

    2.6.3.1 合并数组

    //合并数组
    //ES5用法,合并数组
    let more=[3,4]
    let resultArr=[1,2].concat(more)
    console.log(resultArr)
    //Array(4) [1, 2, 3, 4]
    
    //ES6用法,合并数组
    let resultArr2=[[1,2],...more]
    console.log(resultArr2)
    resultArr2.push(5)
    console.log(resultArr2)
    //[...]之后的数组是一个新数组
    //Array(3) [Array(2), 3, 4]
    resultArr2=[...[1,2],...more]
    console.log(resultArr2)
    //Array(4) [1, 2, 3, 4]
    
    

    2.6.3.2 解析赋值

    扩展运算符可以与解构赋值结合起来,用于生成数组。

    let testArr=[1,2,3,4,5]
    //ES5语法,生成数组
    let a=testArr[0]
    console.log(a)
    //1
    let rest=testArr.slice(1,5)
    console.log(rest)
    //Array(4) [2, 3, 4, 5]
    
    //ES6语法
    //  [first, ...rest2] = [1, 2, 3, 4, 5]
    //会报错,Unexpected token '...'
    const [first, ...rest2] = [1, 2, 3, 4, 5];  
    console.log(first)
    //1
    console.log(rest2)
    //Array(4) [2, 3, 4, 5]
    
    //扩展运算符只能放在参数的最后一位,否则会报错
    const [...butLast, last] = [1, 2, 3, 4, 5];  
    //  报错 ,SyntaxError: Rest element must be last element
    const [first, ...middle, last] = [1, 2, 3, 4, 5];  
    //  报错 ,SyntaxError: Rest element must be last element
    
    

    2.6.4 用于对象

    对象中的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

    let bar={a:1,b:2}
    console.log(bar)
    //Object {a: 1, b: 2}
    
    //ES5复制一个相同的对象,浅拷贝
    let ba=Object.assign({},bar)
    console.log(ba)
    //Object {a: 1, b: 2}
    ba.c=5
    console.log(ba)
    //Object {a: 1, b: 2, c: 5}
    
    //浅拷贝
    let barr={a:1,b:2}
    let bb=barr
    console.log(bb)
    //Object {a: 1, b: 2}
    bb.c=5
    console.log(bb)
    //Object {a: 1, b: 2, c: 5}
    console.log(barr)
    //Object {a: 1, b: 2, c: 5},浅拷贝修改一个对象,相关联的对象都会被修改
    //引用数据类型比如Array,在拷贝的时候拷贝的是对象的引用,当原对象发生变化的时候,拷贝对象也跟着变化
    
    
    //ES6语法,浅拷贝一个相同的对象
    let baz={...bar}
    console.log(baz)
    //Object {a: 1, b: 2}
    baz.c=3
    console.log(baz)
    //Object {a: 1, b: 2, c: 3},baz是一个新的对象,对新的对象操作不会影响之前的对象
    console.log(bar)
    //Object {a: 1, b: 2}
    
    
    
    let obj1={a:1,b:2}
    let obj2={...obj1,b:44}
    console.log(obj1)
    //Object {a: 1, b: 2}
    console.log(obj2)
    //Object {a: 1, b: 44}
    //上面这个例子扩展运算符拷贝的对象是基础数据类型,因此对obj2的修改并不会影响obj1
    
    let objj1={a:1,b:2,c:{nikeName: 'd'}}
    let objj2={...objj1}
    objj2.c.nikeName="ddd"
    
    console.log(objj2)
    // {a: 1, b: 2, c: {…}}
    // a: 1
    // b: 2
    // c: {nikeName: "ddd"}
    // __proto__: Object
    
    console.log(objj1)
    // {a: 1, b: 2, c: {…}}
    // a: 1
    // b: 2
    // c: {nikeName: "ddd"}
    // __proto__: Object
    
    // 这里可以看到,对obj2的修改影响到了被拷贝对象obj1,原因上面已经说了,因为obj1中的对象c是一个引用数据类型,拷贝的时候拷贝的是对象的引用。
    
    
    //es5 中的合并
    let  obj1  = { name:  '张三' }
    let  obj2  = { age:  9 };
    let  obj  = {}
    Object.assign(obj, obj1, obj2)
    console.log(obj)//{ name: '张三', age: 9 }
    //es6 中的合并
    let  obj1  = { name:  '张三' }
    let  obj2  = { age:  9 };
    let obj = { ...obj1, ...obj2 }
    console.log(obj)//{ name: '张三', age: 9 }
    
    

    2.6.5 用于字符串

    扩展运算符还可以将字符串转为真正的数组。

    //字符串
    //扩展运算符还可以将字符串转为真正的数组。
    let str=[...'hello']
    console.log(str)
    //Array(5) ["h", "e", "l", "l", "o"]
    
    
    console.log('xuD83DuDE80y'.length)//4,错误的,怎么识别呢
    
    //扩展运算符能够识别32位的Unicode字符
    
    function length(str) {  
        return [...str].length;  
    }  
    console.log(length('xuD83DuDE80y')) // 3  
    
    

    2.6.6 用于实现了Iterator接口的对象

    任何实现了Iterator接口的对象,都可以用扩展运算符转为真正的数组。

    var nodeList = document.querySelectorAll('div');  
    var array = [...nodeList];  
    
    

    上面代码中,querySelectorAll方法返回的是一个nodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator 接口。

    接下来看下set哈

    但是对于那边没有部署Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组

    let arrayLike={
        '0':'a',
        '1':'b',
        '2':'c',
        length:3    
    }
    // let arr=[...arrayLike]
    //TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator)
    
    let arr2=Array.from(arrayLike)
    console.log(arr2)
    //Array(3) ["a", "b", "c"]
    
    

    删除代码中,arrayList是一个类似数组的对象,但是没有部署Itertator接口,扩展运算符就会报错。这是,可以改为Array.from方法将arrayLike转化为真正的数组。

    2.6.7 Map和Set结构,Generator函数

    扩展运算符内部调用的是数据结构的Iterator 接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map和set,如下代码:

    let map=new Map([[1, 'one'],[2, 'two'],[3,'three']])
    console.log(map.keys())
    //MapIterator {}
    console.log(...map.keys())
    //1 2 3
    
    

    Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。

    var go = function*(){  
        yield 1;  
        yield 2;  
        yield 3;  
        };  
    console.log( [...go()]) // [1, 2, 3]  
    //上面代码中,变量go是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。
    
    

    如果对没有iterator接口的对象,使用扩展运算符,将会报错。

    var obj = {a: 1, b: 2};  
    let arr = [...obj]; 
    // TypeError: Cannot spread non-iterable object
    
    

    2.7 set和Map

    set的基本用法:

    //set接收参数是可遍历的对象,不只是数组
    let ss=new Set([1,2,3,4])
    console.log(ss)
    
    //set添加数据,可以通过add函数
    let s=new Set()
    s.add('hello')
    s.add('goodbye')
    s.add('he').add('good').add('good')
    console.log(s)
    //Set(4) {}
    
    //set删除数据
    s.delete('hello')
    console.log(s)
    //Set(3) {}
    
    //查找某个元素是否存在
    let aa=s.has('he')
    console.log(aa)
    //true
    
    //读取数据
    console.log(s.keys())
    //SetIterator {'he'}
    console.log(s.values())
    //SetIterator {'he'}
    console.log(s.entries())
    //SetIterator {'he'=>'he'}
    
    s.forEach(item=>{
        console.log(item)
    })
    // goodbye
    // he
    // good
    
    for(let item of s) {
        console.log(item)
    }
    // goodbye
    // he
    // good
    
    //修改,set对象比并没有提供方法,可以删除再添加的形式
    
    
    //set清空数据
    s.clear()//
    
    

    map的用法

    //Map的基本用法
    //Map要传入一个可遍历的对象
    let map=new Map([[1,2],[3,4]])
    console.log(map)
    //Map(1) {1=>2,3=>4}
    // let map2=new Map([1,2])
    //出错了,TypeError: Iterator value 1 is not an entry object
    
    //Map里面key可以是任意值,可以是函数或者变量
    //Map添加数据
    map.set(1,2)
    console.log(map)
    //Map(1) {1=>2,3=>4}
    
    //修改key对应的值
    map.set(1,3)
    //Map(1) {1=>3,3=>4}
    
    //Map删除元素,可以删除指定key,而不是value
    map.delete(1)
    console.log(map)
    //Map(1) {3=>4}
    
    //统计Map的大小
    console.log(map.size)
    //1
    
    //查找数据,查找的是key值
    console.log(map.has(1))
    //false
    
    //取值
    console.log(map.get(3))
    //4
    
    
    
    //遍历的顺序和添加到map的顺序有关系
    console.log(map.keys(),map.values(),map.entries())
    //MapIterator {} MapIterator {} MapIterator {}
    
    //forEach的第一个参数是value,不是key,value和key的顺序不能调换
    map.forEach((value,key)=>{
        console.log(value,key)
        //4 3
    })
    
    for(let [key,value] of map) {
        console.log(key,value)
        //3 4
    }
    
    //设置函数为key
    let o=function() {
        console.log('o')
    }
    map.set(o,4)
    console.log(map.get(o))
    //4
    
    //清除数据
    map.clear()
    
    
    

    2.8 复制一个对象

    ES5中怎么把一个对象复制到另一个对象上?

    ES6中怎么做呢?

    //复制对象
    const target = {};
    const source = { b: 4, c: 5 };
    //ES5中我们需要遍历对象,将对象属性一个个拷贝到另一个对象
    
    //ES6中拷贝对象,使用assign方法
    Object.assign(target, source);
    console.log(target);
    //Object {b: 4, c: 5}
    console.log(source);
    //Object {b: 4, c: 5}
    
    //缺陷,浅拷贝对象,assgin实现的是浅复制
    //当对象是引用类型时,浅拷贝只是将引用类型指向的位置改变
    const source1 = { a: { b: { c: { d: 1 } }, e: 2 } };
    const target1 = { a: { b: { c: { d: 3 } }, e: 3, f: 5 } };
    Object.assign(target1, source1);
    console.log(target1, "target");
    // Object {a: Object}
    //target
    // a:
    // b:
    // c:
    // d:1
    // e:2
    //上面的结果可以看出,当是引用类型时,target1和source1的属性被替换成了
    //source1的属性值,而且target1中原先有的属性值a.f也被替换掉了
    //是因为a是一个引用类型的对象,使用assign浅拷贝之后,直接修改的是
    //a对象指向的地址,而不管其中包含的内容,这是浅复制的漏洞
    
    //深拷贝对象时,如果发现嵌套的对象,一定要递归遍历嵌套的对象
    
    

    2.9 创建数组

    //创建新数组
    
    //ES5创建新数组
    let array = Array(5);
    let array2 = ["", ""];
    
    //ES6创建新数组
    //Array.from方法
    let array3 = Array.from([1, 2]);
    console.log(array3);
    //Array(2) [1, 2]
    
    //把n个数据放到数组里面,Array.of
    let array4 = Array.of(1, 2, 3, 45);
    console.log(array4);
    //Array(4) [1, 2, 3, 45]
    let arry5 = Array.of(...array4);
    console.log(arry5);
    //Array(4) [1, 2, 3, 45]
    
    //Array.fill
    let array6 = Array(5).fill(1);
    console.log(array6);
    //Array(5)[1, 1, 1, 1, 1]
    //Array.fill(value,start,end)
    
    //如何替换数组的某一块区域
    let array7 = [1, 2, 3, 4, 5];
    array7.fill(8, 2, 4);
    console.log(array7);
    //[1, 2, 8, 8, 5]
    
    let array8 = Array(5).fill({ a: 1 });
    console.log(array8);
    //(5) [{…}, {…}, {…}, {…}, {…}]
    // 0: {a: 1}
    // 1: {a: 1}
    // 2: {a: 1}
    // 3: {a: 1}
    // 4: {a: 1}
    
    array8.fill({ b: 3 }, 1, 2);
    //(5) [{…}, {…}, {…}, {…}, {…}]
    // 0: {a: 1}
    // 1: {b: 3}
    // 2: {a: 1}
    // 3: {a: 1}
    // 4: {a: 1}
    
    
    

    2.10 数组查找元素

    ES5--可以用map ,forin,filter

    ES6--find,findIndex

    2.11 正则表达式

    //ES6中的y修饰符是什么含义?

    //ES5支持y修饰符吗

    2.12 数组的解构赋值

    //ES5中从一个复杂的数组结构中提取数据
    //ES6中有什么方式呢?
    
    //ES5中一个个根据索引值取数据
    let arr = ["hello", "world"];
    let firName = arr[0];
    let secondName = arr[1];
    console.log(firName, secondName);
    //hello world
    
    //ES6中利用结构赋值的方式
    //数组的解构赋值,左边是中括号
    let arr2 = ["hello", "world"];
    let [firstName, secName] = arr2;
    console.log(firstName, secName);
    //hello world
    
    //Array|Object存储数据
    
    //数组很长,只关注某一项,用,来跳过中间的某一项
    let arr3 = ["a", "b", "c", "d"];
    let [one, , three] = arr3;
    console.log(one, three);
    //a c
    
    //什么时候可以解构赋值呢,发现下面的
    //字符串也可以解构赋值
    let str = "abcbd";
    let [oneStr, , threeStr] = str;
    console.log(oneStr, threeStr);
    //a c
    
    //凡是可遍历的对象,都可以解构赋值
    //比如set,map,对象,数组,字符串
    
    let [a, , b] = new Set([1, 3, 4, 5]);
    console.log(a, b);
    //1 4
    //取第1个值和第3个值
    
    
    //如何利用数组给对象的属性重新赋值
    let user = { name: "c", surName: "d" };
    //ES5
    user.name = arr[0];
    user.surName = arr[1];
    //ES6
    [user.name, user.surName] = [1, 2];
    console.log(user.name, user.surName);
    //1 2
    
    //这里左边中括号里面是对象的属性名称
    //所以左边没有let或者const的声明
    //和上面不同的是,上面是变量名称
    //变量名称,前面有let或const声明
    
    //解构赋值,不仅可以赋值简单的变量
    //还可以赋值对象的属性
    
    //循环里面赋值
    let arr2 = ["hello", "world"];
    for (let i = 0, item; i < arr2.length; i++) {
      item = arr[i];
      //声明一个临时变量来保存数据
    }
    
    //ES6,解构赋值的另一种形式
    //Object.entries将对象转换为可遍历的方法
    for (let [k, v] of Object.entries(user)) {
      //隐式赋值
      console.log(k, v);
    }
    // name 1
    // surName 2
    
    
    //4.rest变量
    let arr4 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    let [firName, curName] = arr4;
    console.log(firName, curName);
    //1 2
    //这里,只用到第1个元素和第2个元素
    //那其他的元素怎么办呢?
    //不想让浏览器清除其他元素
    //用...来保存其他元素
    let [one, two, ...last] = arr4;
    console.log(one, two, last);
    // 1 2
    //Array(7) [3, 4, 5, 6, 7, 8, 9]
    
    //极端情况;
    let arr5 = [];
    let [oo, pp, ...last2] = arr5;
    console.log(oo, pp, last2);
    // undefined undefined []
    //如果进行解构赋值的数组没有那么多值,
    //取到的值是[]
    //这时可以给这些undefined的变量给个默认值
    // let [oo = "a", pp = "b", ...last2] 
    
    

    2.13 object的解构赋值

    let options = {
      title: "menu",
       100,
      height: 200,
    };
    //object解构赋值,左边是{}花括号,变量名称要和对象属性名称一样
    //数组的是[]中括号
    let { title, width, height } = options;
    console.log(title, width, height);
    //menu 100 200
    
    //左边{}里面的变量名称必须和对象的属性相同
    //是因为用了简写的方式
    
    //如果不用简写的方式,就可以不用一致,用下面的方式,
    let { title: title2 } = options;
    console.log(title2);
    //menu
    
    //如果有2个对象有相同的属性呢
    //关于默认值的问题
    let { title: title3, area = 100 } = options;
    console.log(title3, area);
    // menu 100
    //options里面没有area属性,所以该属性取默认值
    
    //没有存储的对象属性怎么办呢?
    //用...rest变量
    let { title: title4, ...last } = options;
    console.log(title4, last);
    // menu
    // Object { 100, height: 200}
    
    //实际开发过程中,接口返回的数据一般都会有多层嵌套
    //嵌套的属性怎么办呢?
    let options2 = {
      size: {
        width2: 100,
        height2: 200,
      },
      item: ["cake", "Donet"],
      extra: true,
    };
    //解构时需要嵌套一一匹配
    //需要和解构数据的结构保持一致
    let {
      size: { width2, height2 },
      item: [item1, item2],
      extra,
    } = options2;
    console.log(width, height2, item1, extra);
    // 100 200 cake true
    //item属性是个数组,可以用数组解构赋值[]
    //size属性是个对象,可以用对象解构赋值{}
    
    

    业务开发中,可以考虑对接口数据进行解构赋值。

    2.14 Promise(异步回调)

    如果想在一个函数执行结束之后,调用另外一个函数,这就是回调了。

    ES5中的回调地狱

    函数A调用函数B,函数B调用函数C,函数C调用函数D,层层嵌套造成回调地狱。这个非常难以维护。

    ES6是如何通过异步操作来实现的呢?

    通过加载js文件的实例来说明这个知识点:

    在项目的src/static/目录下面新建1.js文件,文件内容是:

    console.log(1);
    
    

    在项目的src/static/目录下面新建2.js文件,文件内容是:

    console.log(2);
    
    

    在项目的src/static/目录下面新建3.js文件,文件内容是:

    console.log(3);
    
    

    在src目录下面的index.html文件内容如下:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>ES6 入门导学</title>
      </head>
      <body>
        <h1>h33</h1>
        <script src="./static/demo.1.js" charset="utf-8"></script>
      </body>
    </html>
    
    
    

    在src/static/目录下面的demo.1.js,文件内容如下:

    //Promise
    //通过异步加载JS文件的例子来理解
    function loadScript(src) {
      let script = document.createElement("script");
      script.src = src;
      document.head.append(script);
    }
    
    loadScript("./static/1.js");
    
    
    

    在浏览器中运行index.html文件,发现控制台中输出1,这是1.js的文件内容,说明代码已经起作用了。

    //Promise
    //通过异步加载JS文件的例子来理解
    function loadScript(src) {
      let script = document.createElement("script");
      script.src = src;
      document.head.append(script);
    }
    
    function test() {
      console.log("test");
    }
    
    loadScript("./static/1.js");
    test();
    //控制台中输出
    //test
    //1
    
    //虽然我们先执行的是loadScript函数,后执行的是test函数
    //但是最后的打印结果显示,先执行test内容,再执行load内容
    //这是因为js引擎的特点,js是单线程的
    //实际,确实先执行的loadScript函数,但是这是个异步操作,静态资源加载是//一个异步操作,会把该操作放到异步队列里面
    //loadScript代码执行之后,立刻会解析test函数的代码,不会等
    //loadScript代码的内容执行结束之后,再执行其他函数
    //等单线程执行结束之后,才会去执行异步队列里面的内容
    
    //异步操作不会立即执行,会放到异步队列里面去
    //同步操作会立即执行,优先同步操作
    
    //这样写是达不到我们的效果,那么怎么写才合适呢?利用回调
    
    

    如果我们直接将test函数传给loadScript函数,那么loadScript函数里面直接执行test函数能生效吗

    function loadScript(src, callback) {
      let script = document.createElement("script");
      script.src = src;
      callback();
      document.head.append(script);
    }
    
    function test() {
      console.log("test");
    }
    
    loadScript("./static/1.js", test);
    
    //控制台中输出
    //test
    //1
    //这种方法并没有生效
    //是因为加载脚本是异步的过程,而loadscript函数里面是同步的过程
    
    
    function loadScript(src, callback) {
      let script = document.createElement("script");
      script.src = src;
      //当静态资源加载的异步操作结束之后会调用onload函数
      //那么通过监听这个onload事件来执行callback
      script.onload = () => {
        callback();
      };
      document.head.append(script);
    }
    
    function test() {
      console.log("test");
    }
    
    loadScript("./static/1.js", test);
    
    //控制台中输出
    //1
    //test
    
    //该方法是生效的,可以达到我们的效果
    
    

    那么如何给callback回调函数里面传递参数呢?

    //Promise
    //通过异步加载JS文件的例子来理解
    function loadScript(src, callback) {
      let script = document.createElement("script");
      script.src = src;
      //当静态资源加载的异步操作结束之后会调用onload函数
      //那么通过监听这个onload事件来执行callback
      script.onload = () => {
        callback(src);
      };
      document.head.append(script);
    }
    
    function test(src) {
      console.log(src);
    }
    
    loadScript("./static/1.js", test);
    
    //控制台中输出
    //1
    //./static/1.js
    //这样就可以动态给回调函数传值了
    
    

    上面的例子实现了一个最简单的回调,那如果要实现多个回调呢?

    //Promise
    //通过异步加载JS文件的例子来理解
    //通过加载多个js文件,1.js加载完了加载2.js,最后再加载3.js
    function loadScript(src, callback) {
      let script = document.createElement("script");
      script.src = src;
      //当静态资源加载的异步操作结束之后会调用onload函数
      //那么通过监听这个onload事件来执行callback
      script.onload = () => {
        callback(src);
      };
      document.head.append(script);
    }
    
    function test(src) {
      console.log(src);
    }
    
     loadScript("./static/1.js", function (script) {
       loadScript("./static/2.js", function (script) {
         loadScript("./static/3.js", function (script) {});
       });
     });
    
    //控制台中输出
    //1
    //2
    //3
    
    //通过层层回调是可以实现我们想要的效果的
    //但是这个写法很复杂,特别是我们回调结束之后还要写一些业务逻辑的代码
    //如下面的代码,函数就会很复杂,行数也很多
    //通过一个函数不要超过200行,下面的写法很容易超过200行
    
    loadScript("./static/1.js", function (script) {
      console.log(script);
      loadScript("./static/2.js", function (script) {
        console.log(script);
        loadScript("./static/3.js", function (script) {
          console.log(script);
        });
      });
    });
    
    //控制台中输出
    //1
    //./static/1.js
    //2
    //./static/2.js
    //3
    //./static/3.js
    
    //如果调用函数过程中发生了错误呢?静态资源不存在,我们怎么监控呢?
    //如果在这个函数里面再处理很多逻辑的话,
    
    

    回调地狱

    //Promise
    //通过异步加载JS文件的例子来理解
    //通过加载多个js文件,1.js加载完了加载2.js,最后再加载3.js
    function loadScript(src, callback) {
      let script = document.createElement("script");
      script.src = src;
      //当静态资源加载的异步操作结束之后会调用onload函数
      //那么通过监听这个onload事件来执行callback
      script.onload = () => {
        callback(src);
      };
      script.onerr = (err) => {
        callback(err);
      };
      document.head.append(script);
    }
    
    function test(src) {
      console.log(src);
    }
    
    loadScript("./static/1.js", function (script) {
      if (script.message) {
        //监控上报逻辑
        console.log(script);
        loadScript("./static/2.js", function (script) {
          console.log(script);
        });
      } else {
        console.log(script);
        loadScript("./static/2.js", function (script) {
          console.log(script);
          loadScript("./static/3.js", function (script) {
            console.log(script);
          });
        });
      }
    });
    //上面的代码,不同的业务逻辑,层层嵌套导致回调的层数很深
    //后续维护很难维护,这就是回调地狱
    
    //ES5中没法处理回调地狱的问题
    //引入ES6的Promise来解决回调地狱的问题
    
    

    引入Promise

    //Promise
    //通过异步加载JS文件的例子来理解
    function loadScript(src) {
      return new Promise((resolve, reject) => {
        let script = document.createElement("script");
        script.src = src;
        script.onload = () => resolve(src);
        script.onerror = (err) => reject(err);
        document.head.append(script);
      });
    }
    //利用promise对象写代码,处理正常和异常代码,代码看起来很好看
    loadScript("./static/1.js");
    //1
    //如何使用呢
    
    //如果想在加载1.js文件之后加载2.js文件,如何实现呢?
    loadScript("./static/1.js").then(loadScript("./static/2.js"));
    //1
    //2
    //成功顺序加载1.js,2.js
    
    //如果想在加载1.js文件之后加载2.js文件再加载3.js,如何实现呢?
    loadScript("./static/1.js")
      .then(loadScript("./static/2.js"))
      .then(loadScript("./static/3.js"));
    //1
    //2
    //3
    
    //这样代码简洁之后,后期维护就会变得很简单
    
    
    

    为什么可以这样执行呢

    function loadScript(src) {
      //pending,undefined
      //new Promise的时候返回了一个状态,叫pending
      //promise的结果是result,是undefined,并没有值
      return new Promise((resolve, reject) => {
        let script = document.createElement("script");
        script.src = src;
        //promise执行结束之后,会返回resolve/reject
        //返回resolve/reject时,promise的状态就改变
        script.onload = () => resolve(src); //fulfilled,result
        script.onerror = (err) => reject(err);//rejected,error
        document.head.append(script);
      });
    }
    
    
    //promise--执行的时候是pending状态
    //promise-fulfilled完成之后执行结束之后是resovle和reject状态
    
    

    2.14.1 Promise-then操作

    promise里面的then是怎么调用的呢?

    //.then是promise原型链上的方法,promise对象才可以调用then方法
    //promise.then(onFulfilled,onRejected)
    
    //then里面接受的的是函数类型
    //loadScript("./static/2.js")返回的是promise对象,不是函数
    
    //上面例子里面的then里面的2个参数是非函数,是忽略这里面的内容的
    //如果then里面是非函数,会返回空的promise对象
    //then里面是个表达式,表达式是会正常执行的,
    
    //接下来我们修改一下,修改为和then里面一致的函数参数
    //then返回的是promise实例
    
    loadScript("./static/1.js").then(
      () => {
        loadScript("./static/2.js");
      },
      (err) => {
        console.log(err);
      }
    );
    //1
    //2
    //可以正常输出
    
    loadScript("./static/4.js").then(
      () => {
        loadScript("./static/2.js");
      },
      (err) => {
        console.log(err);
      }
    );
    //4.js不存在,控制台会报错,66行会输出报错信息
    // net::ERR_FILE_NOT_FOUND
    // Event {isTrusted: true, type: "error", target: script, currentTarget: script, eventPhase: 2, …}
    
    loadScript("./static/4.js")
      .then(
        () => {
          loadScript("./static/2.js");
        },
        (err) => {
          console.log(err);
        }
      )
      .then(
        () => {
          loadScript("./static/3.js");
        },
        (err) => {
          console.log(err);
        }
      );
    // net::ERR_FILE_NOT_FOUND
    // Event {isTrusted: true, type: "error", target: script,
    // 3
    
    //同理,控制台第一个then里面就会捕获到异常错误,并打印输出
    //但是第2个loadScript("./static/3.js")是被执行的
    //是因为第1个then里面虽然打印了错误信息,但是返回了一个新的promise实例
    //所以第2个then里面才会被执行
    
    
    loadScript("./static/1.js")
      .then(
        () => {
          loadScript("./static/4.js");
        },
        (err) => {
          console.log(err);
        }
      )
      .then(
        () => {
          loadScript("./static/3.js");
        },
        (err) => {
          console.log(err);
        }
      );
    // 1
    // net::ERR_FILE_NOT_FOUND
    // Event {isTrusted: true, type: "error", target: script,
    // 3
    
    //当在执行第一个then里面的loadScript("./static/4.js")
    //方法的时候,会抛出异常,那其实最后面的then里面的loadScript
    //不应该被执行了,但从结果来看,还是被执行了
    
    //这是因为当then里面没有返回promise对象的时候,会返回一个
    //空的promise对象,该对象是resolved状态,所以3.js会被执行
    
    //如何解决这个问题呢?在第一个then里面loadScript前面添加return
    //即可以返回一个promise对象,再下面的then就正常执行了
    
    loadScript("./static/1.js")
      .then(
        () => {
          return loadScript("./static/4.js");
        },
        (err) => {
          console.log(err);
        }
      )
      .then(
        () => {
          loadScript("./static/3.js");
        },
        (err) => {
          console.log(err);
        }
      );
    //1
    // net::ERR_FILE_NOT_FOUND
    // Event {isTrusted: true, type: "error", target: script,
    
    

    2.14.2 Promise-Resolve-Reject操作

    利用Promise的静态方法,Resolve和Reject方法可以将同步操作转换为异步操作,这是工作中的一个小技巧。

    function test(bool) {
      if (bool) {
        return new Promise();
      } else {
        // return 42;
        return Promise.resolve(42);
        //利用Promise的静态方法,将数字转换为promise
      }
    }
    //如果是数字如何使用promise呢?将其转换为promise
    test(0).then((value) => {
      console.log(value);
    });
    //42
    //这样的方式就可以拿到传进来的值了
    
    test(1).then((value) => {
      console.log(value);
    });
    // demo.1.js:154 Uncaught TypeError: Promise resolver undefined is not a function
    //     at new Promise (<anonymous>)
    //     at test (demo.1.js:154)
    //     at demo.1.js:168
    //因为bool为true的时候,promise里面没有传递resolve的值
    
    
    //修改为下面的方式,就不会报错了
    function test(bool) {
      if (bool) {
        return new Promise((resolve, reject) => {
          resolve(30);
        });
      } else {
        // return 42;
        return Promise.resolve(42);
        //利用Promise的静态方法,将数字转换为promise
      }
    }
    test(1).then((value) => {
      console.log(value);
    });
    //30
    
    //通过Promise的resolve/reject方法,可以将同步操作转换为异步操作了
    
    //reject怎么用呢???
    function test(bool) {
      if (bool) {
        return new Promise((resolve, reject) => {
          resolve(30);
        });
      } else {
        // return 42;
        return Promise.reject(new Error("42"));
        //利用Promise的静态方法,将数字转换为promise
      }
    }
    test(0).then(
      (value) => {
        console.log(value);
      },
      (err) => {
        console.log(err);
      }
    );
    //30
    //Error: 42
    
    //通过Promise的resolve/reject的2个静态方法,可以将同步操作转换为异步操作了
    
    

    2.14.3 Promise-Catch异常处理操作

    如果在每调用一个then方法,then方法里面都去处理错误,代码看起来很麻烦,有没有更优雅的方式呢?

    function loadScript(src) {
      //pending,undefined
      //new Promise的时候返回了一个状态,叫pending
      //promise的结果是result,是undefined,并没有值
      return new Promise((resolve, reject) => {
        let script = document.createElement("script");
        script.src = src;
        //promise执行结束之后,会返回resolve/reject
        //返回resolve/reject时,promise的状态就改变
        script.onload = () => resolve(src); //fulfilled,result
        script.onerror = (err) => reject(err); //rejected,error
        document.head.append(script);
      });
    }
    
    loadScript("./static/1.js")
      .then(() => {
        return loadScript("./static/4.js");
      })
      .then(() => {
        return loadScript("./static/3.js");
      })
      .catch((err) => {
        console.log(err);
      });
    
    //1
    //net::ERR_FILE_NOT_FOUND
    //Event {isTrusted: true, type: "error", target: script, currentTarget: s
    
    //这样捕获到loadScript("./static/4.js")的错误的时候,就不会往下面执行了
    //这种方式是可以捕获到所有的loadScript执行的错误信息的
    
    //注意,catch是promise实例的方法,是promise原型链上的方法,不是静态方法
    //promise是用reject的方式来抛出异常的,而不能通过throw new Error的方法来进行
    //因为catch捕获的是改变promise状态变成reject的错误
    
    

    2.14.4 ES8中的Promise(2017 ES8)

    ES8中对promise进行了优化

    //函数前面添加async关键词
    async function firstAsync() {
      return 27;
    }
    
    console.log(firstAsync());
    // Promise {<resolved>: 27}
    // __proto__: Promise
    // [[PromiseStatus]]: "resolved"
    // [[PromiseValue]]: 27
    
    //打印函数返回的值,返回的是promise对象和普通的函数不同
    //promise对象的状态是resolved
    
    //前面是通过new Promise来进行异步操作的
    //async的作用:不需要手动返回promise对象,就可以调用then方法了
    //async用在函数前面,会把函数返回值转换为promise对象,转换为异步操作
    
    //async函数的返回值如果是个promise对象,就不会做处理了
    //async函数的返回值如果不是promise实例,就会将其转化为promise对象了
    console.log(firstAsync().then());
    //Promise {<pending>}
    // __proto__: Promise
    // [[PromiseStatus]]: "resolved"
    // [[PromiseValue]]: 27
    
    //那么如何得到promise对象里面的值呢
    console.log(
      firstAsync().then((value) => {
        console.log(value);
      })
    );
    //Promise {<pending>}
    // __proto__: Promise
    // [[PromiseStatus]]: "resolved"
    // [[PromiseValue]]: undefined
    //27
    
    //利用then里面的回调函数可以打印出promise里面的值27
    //27上面打印的是firstAsync().then返回的promise对象,值为undefined
    
    //27是如何传递给promise对象,
    //return 27 可以理解为return Promise.resolve(27)
    //加了async关键词,浏览器引擎会进行自动转换,非promise转为promise实例
    //如下代码,更清楚看出async返回的是promise对象
    console.log(firstAsync() instanceof Promise);
    //true
    
    

    如果async函数里面还要调用其他的异步函数,怎么办呢?是不是还要使用promise?其实不是,利用ES8中的await来实现

    //async异步函数里面有异步操作
    //异步操作是1s后执行resolve(setTimeout函数)
    async function firstAsync() {
      //声明promise
      let promise = new Promise((resolve, reject) => {
        setTimeout(function () {
          resolve("now it is done");
        }, 1000);
      });
      //输出promise的内容
      promise.then((val) => {
        console.log(val);
      });
      console.log(2);
      return 3;
    }
    
    firstAsync().then((val) => {
      console.log(val);
    });
    //2
    //3
    //now it is done
    
    //发现输出的内容和我们想的不太一样
    //promise的输出内容是最后才执行的
    
    
    //async异步函数里面有异步操作
    //异步操作是1s后执行resolve(setTimeout函数)
    //如果我们想先执行异步操作,再执行后面的操作
    //下面的方法似乎也行不通
    async function firstAsync() {
      //声明promise
      let promise = new Promise((resolve, reject) => {
        setTimeout(function () {
          resolve("now it is done");
        }, 1000);
      });
      //输出promise的内容
      promise.then((val) => {
        console.log(val);
        console.log(2);
        return 3;
      });
    }
    
    firstAsync().then((val) => {
      console.log(val);
    });
    // undefined
    // now it is done
    // 2
    
    //我们想着把后面的内容放到async里面promise的回调函数里面
    //去实现,发现还是不行的
    
    
    

    那么如何让异步函数里面的异步操作按照顺序执行呢?

    //async异步函数里面有异步操作
    //异步操作是1s后执行resolve(setTimeout函数)
    //如果我们先执行异步操作,再执行后面的操作
    async function firstAsync() {
      //声明promise
      let promise = new Promise((resolve, reject) => {
        setTimeout(function () {
          resolve("now it is done");
        }, 1000);
      });
      //引入await
      let result = await promise;
      console.log(result);
      // console.log(await promise);
      console.log(2);
      return 3;
    }
    
    firstAsync().then((val) => {
      console.log(val);
    });
    
    //now it is done
    //2
    //3
    
    //发现引入await函数的执行方式和我们预期想的一样
    //直接使用console.log(await promise) 来执行也是一样的
    //await promise 是一个表达式
    //表达式的值就是promise返回的值
    
    
    

    如果await 后面不是promise,是一个数组怎么办呢?

    //async异步函数里面有异步操作
    //异步操作是1s后执行resolve(setTimeout函数)
    //如果我们先执行异步操作,再执行后面的操作
    async function firstAsync() {
      //声明promise
      let promise = new Promise((resolve, reject) => {
        setTimeout(function () {
          resolve("now it is done");
        }, 1000);
      });
      //引入await
      // let result = await promise;
      // console.log(result);
      console.log(await promise);
      console.log(await 30);
      console.log(await Promise.resolve(40));
      console.log(2);
      return 3;
    }
    
    firstAsync().then((val) => {
      console.log(val);
    });
    
    //now it is done
    //30
    //40
    //2
    //3
    
    //await后面跟的一定是promise实例
    //await后面跟一个非promise实例,返回的也是promise
    //上面例子中的30和40,可以看出await会将非promise实例转换为promise实例
    
    //在普通函数里面是不能用await方法的,await一定要用在async函数里面
    //async和await其实是promise的语法糖,其实背后还是promise的运作原理
    //只是使用方式更加简洁
    
    
    

    2.14.5 ES9中的for await of 和finally(2018 ES9)

    ES9中异步操作集合是如何遍历的呢?

    //生成一个promise对象,time时间之后输出time的内容
    function Gen(time) {
      return new Promise((resolve, reject) => {
        setTimeout(function () {
          resolve(time);
        }, time);
      });
    }
    
    //生成异步操作的集合
    async function test() {
      let arr = [Gen(2000), Gen(100), Gen(3000)];
      for (let item of arr) {
        console.log(Date.now(), item.then(console.log));
      }
    }
    
    test();
    //1587290683537 Promise {<pending>}
    //1587290683537 Promise {<pending>}
    //1587290683537 Promise {<pending>}
    // 100
    // 2000
    // 3000
    
    //函数执行结果可以发现,forof依次遍历异步操作的集合元素,
    //遍历的时候,异步对象还没有执行结束,所以返回的是pending状态的promise
    
    //所以forof不能够遍历执行异步操作的对象
    
    
    

    思考一下,如果我们在forof遍历的时候使用await呢?

    //生成一个promise对象,time时间之后输出time的内容
    function Gen(time) {
      return new Promise((resolve, reject) => {
        setTimeout(function () {
          resolve(time);
        }, time);
      });
    }
    
    //生成异步操作的集合
    async function test() {
      let arr = [Gen(2000), Gen(100), Gen(3000)];
      for (let item of arr) {
        console.log(Date.now(), await item.then(console.log));
      }
    }
    
    test();
    // 2000
    // 1587290930908 undefined
    // 100
    // 1587290932909 undefined
    // 3000
    // 1587290932909 undefined
    
    //执行结果显示,await是会一直等待promise指向结束,才会让
    //for循环指向下一个遍历循环,
    //js是单线程的,所以会一直等待上一个await执行结束,再执行后面的
    //这个结果也不是我们的预期
    //比单纯使用forof,已经可以按照顺序执行了
    
    

    引入for await of

    //生成一个promise对象,time时间之后输出time的内容
    function Gen(time) {
      return new Promise((resolve, reject) => {
        setTimeout(function () {
          resolve(time);
        }, time);
      });
    }
    
    //生成异步操作的集合
    async function test() {
      let arr = [Gen(2000), Gen(100), Gen(3000)];
      for await (let item of arr) {
        console.log(Date.now(), item);
      }
    }
    
    test();
    // 1587291517030 2000
    // 1587291517031 100
    // 1587291518036 3000
    
    //执行结果显示,for await of是会一直等待上个promise执行结束
    //并正确输出promise的值,然后执行下个promise
    
    //forof是用来遍历同步操作的
    //forof里面应用await是有问题的
    //for await of是用来遍历异步操作的
    
    
    

    ES9中Promise是如何进行兜底操作呢?

    如果正常连接数据库和异常异常了,最后都要进行关闭数据库的操作

    不管promise返回的是resolve和还是reject,都要执行finally操作

    const Gen = (time) => {
      return new Promise((resolve, reject) => {
        setTimeout(function () {
          if (time < 500) {
            reject(time);
          } else {
            resolve(time);
          }
        }, time);
      });
    };
    
    Gen(Math.random() * 1000)
      .then((val) => console.log("resolve", val))
      .catch((err) => console.log("err"))
      .finally(() => console.log("finish"));
    //err
    // finish
    
    //resolve 705.4537090515282
    //finish
    
    //多次执行,发现不管是执行resolve,还是reject,都会执行finally函数//
    
    //如果是个弹框操作,不管返回是then还是catch之后,都要执行弹框的操作
    //这时候就可以抽象放到finally来做,用一个全局变量来存放弹框的内容,
    
    
    
  • 相关阅读:
    教你做一个牛逼的DBA(在大数据下)
    分区扫描执行计划分析简介
    远程桌面无法复制粘贴
    测试链接服务器sql 语句
    webservice 测试窗体只能用于来自本地计算机的请求
    win2003浏览器提示是否需要将当前访问的网站添加到自己信任的站点中去
    如何在excel中把汉字转换成拼音
    关于update set from where
    添加链接服务器的代码
    「android」as过滤svn文件
  • 原文地址:https://www.cnblogs.com/zdjBlog/p/12699533.html
Copyright © 2011-2022 走看看