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来做,用一个全局变量来存放弹框的内容,