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('x\uD83D\uDE80y'.length)//4,错误的,怎么识别呢

//扩展运算符能够识别32位的Unicode字符

function length(str) {  
    return [...str].length;  
}  
console.log(length('x\uD83D\uDE80y')) // 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",
  width: 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 {width: 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来做,用一个全局变量来存放弹框的内容,


posted @ 2020-04-14 17:41  melimeli  阅读(258)  评论(0编辑  收藏  举报