ES6的笔记
## let声明变量
基本用法:类似var ,但是变量只在代码块内有效
var 和 let 比较
{ let x=10; var y=15 } console.log(y)//15 console.log(x)//报错
let不存在变量提升 暂时性死区 es6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了密闭空间。凡是在声明之前就使用这些变量就会报错
简单来说暂时性死区就是在let的本作用域中 在let之前引用了let的变量 let不允许重复声明斜体样式 允许在块级作用域内声明函数 函数声明类似于var ,即会提升到全局作用域或函数作用域的头部,同时函数声明还会提升到所在的块级作用域的头部 避免在块级作用域内声明函数优先使用函数表达式 let fn=function(){} //块级作用域必须有大括号 const const声明的变量为常量,只读,不能更改 const声明变量是立即赋值 const作用域玉let相同,只在声明的块级作用域有效 不存在变量提升 不能重复声明 const保证的是值和引用地址不得更改 const FOO=Object.freeze({a:1,b:2}) 冻结对象本身不能添加属性,不能修改属性的值类型 冻结数组本身不能添加元素,不能修改元素的值类型
//Object.keys(obj)返回一个数组,由obj的所有属性或者obj的所有索引 //深拷贝: function freezeAll(obj){ var ora=Array.isArray(obj)?[]:{}; Object.freeze(obj); //Object.keys(obj)返回一个数组,由obj的所有属性或者obj的所有索引 Object.keys(obj).forEach(function(item){ if(typeof obj[item]=="object"){ ora[item]=freezeAll(obj[item]) }else{ ora[item]=obj[item] } }); return ora; } var obj=[1,[2,3,[4,5,[6,7]]]] var res=freezeAll(obj); //冻结 const AX=freezeAll(obj) function freezeAll(obj){ Object.freeze(obj); // Object.keys(obj)返回一个数组,由obj的所有属性或者obj的所有索引 Object.keys(obj).forEach(function(item){ if(typeof obj[item]=="object"){ obj[item]=freezeAll(obj[item]) } }); return obj; }
let a=1,b=2,c=3; 不注册变量成window的属性 没有重复定义
数组的解构赋值
按一定模式从数组或对象中提取值为变量赋值叫做解构 模式匹配,等号左右两边模式相同,不能解构赋值undefined 部分匹配,左边变量少于右边数组 右边不是数组 表达式惰性求值,需要赋值的时候执行表达式 对象的解构赋值 let{a:a,b:b,c:c}={a:1,b:2,c:3}--模式和变量 对象的解构赋值的内部机制,是先找到同名属性,然后再赋值给对应的变量 等号右边数字和布尔值结构先转为对象
字符串
es6用反引号(ESC下)包围变量用${变量}拼接 字符串的方法:str.includes("s") 有没有 返回true和false str.startsWith("s")是不是在开头 str.endsWith("s")是不是在结尾 str.includes("s",num) str.startsWith("s",num)从索引最后前num是否以s开头 str.endsWith("s",num)从索引最后前num是否以s解束 str.repeat(n)重复次数,生成新字符串n不是数字会被转成数字,n<=-1报错;-1<n<0,n=NAN解果都为0 str.padStart(n,"xyz")从前面补全字符串,生成新字符串 str.padEnd(n,"y")从后面补全字符串 n:补全之后的长度,y要补充的字符串,重复出现 n=str长度:返回str 重复次数过多超过n,截取后面的多余字符 str.trim()//去除空白返回新字符串 str.trimStart()去除前面空白和str.trimLeft()一样 str.trimEnd()去除后面空格和str.trimRight()一样 空白含:空格 tab键空格 全角空格 半角空格 str.replaceAll("a","m")替换全部 Number.isFinite()只对数值有效,其他类型一律false isFinite() Number.isNAN()只对数值有效,其他类型一律false isNAN(); Number.parseInt()==parseInt(); Number.parseFloat()==parseFloat(); Number.isInteger();判断是否为整数,非数值返回false Number.isSafeInteger()判断是否是安全整数(-2的53次方)~(2的53次方) Number.MAX_SELF_INTEGER BigInt-大整数 在数字后面加n(标志) typeof为BigInt() BigInt(x)x为小数报错,x为NAN报错 Boolean String Number可以将大整数转换 运算与Number一致,除以运算舍弃小数10n/3n=3n; 可用于比较1n<2 true ""+35n="35" 运算(1n*10n)
指数
a**b=Math.pow() a的b次方 a**b**c===a**(b**c) Math Math.trunc()去除小数点,保留整数 非数值转化成数值,转化不了NAN Math.sign() 正1 负-1 0-0 -0--0 NAN Math.imul(a,b);a*b
数组Array 扩展运算符…
Math.max(...[123]) let arr=[1,2];var copy=[...arr]; let arr1=[1,2],arr2=[3,4,5] let arr=[...arr1,...arr2] const[first,...rest]=[1,2,3,4,5]; let BtnArr=[...document.querySelectorAll(".btn")] Array.from(),将类数组转化成真正的数组 只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组; 第二个参数回调函数,对数组元素进行处理,将处理之后的值放入返回数组 Array.from(arguments,function(arg){ teturn arg*arg }) 与 ...(扩展运算符)的区别:都是能转化具有Iterator接口的数据为数组 {length:2},...不能转化 Array.from转换对象为[undefined,undefined],...转换对象报错 Map arr.map(function(value,index,arr){ }) 返回新数组,可以对每一项进行修改可以直接通过value改值,forEach不行,只读不写 想要更改要用第三个参数arr[index]进行改值(value = arr【index】*1) 生成1-100 var res=Array.from({length:100}).map(function(v,i){ return v=i+1; }); 或 var res=Array(100).fill(0).map(function(v,i){ return v=i+1; }) arr.fill(x)填充数组,已有的元素会被覆盖 arr.fill(x,start,end)//起始位置,结束位置(不含)只有开始填充到最后 x是引用类型,填的是指向 Array.of();序列转化成数组 Array.of(1,2,3)==[1,2,3]; [].slice.call(arguments)模拟实现Array.of() 序列变数组Array.of() 类数组变数组Array.from() 数组转序列...(扩展运算符) Array.copyWithin(目标索引,开始索引,结束索引) 从开始索引到结束索引,复制数组元素,从目标索引开始放在目标索引 负数为倒数 [1,2,3,4,5].copyWithin(0,3) 4,5,3,4,5 arr.find(function(){})返回第一个符合条件的结果,没有则返回undefined arr.findIndex(function(){})返回第一个符合条件的索引,没有则返回-1 indexOf()不能查找NAN (arr.includes)能查找NAN arr.keys();获取键 arr.values()获取值 arr.entries()获取键值对 返回数组,遍历器对象,可通过for..of遍历 for(let [k,v] of arr.keys()){ //keys() 数组中的每一项 console.log(k,v)//索引和内容 } let arr=[1,2,3,4,5]; for(let i of arr){ console.log(i) 直接取值 } for(let i of arr.keys()){ console.log(i)取索引 } arr.includes(tag,index) arr中是否包含tag index,开始索引,默认0,负数从后往前数 负大于长度0 arr.flat(n);拉平数组 用infinity做参数,无论多少维数组都能拉平为一层 arr.flatMap(function(v,i){ 对每个元素进行处理(map),在拉平,最后返回一个新数组 }) flatMap只能拉平一层 empty,[,,,] empty:什么都没有,不是undefined,不是null forEach,some,every,reduce,filter跳过空元素 map跳过但保留元素显示为(empty) join,toString将空元素视为undefined,undefined和null视为空 Array.from(),...将空视为undefined 将类似数组的对象转化为真的数组 copyWith拷贝空元素 fill将空视为正常 for...of将空视为undefined keys,values,entries,find,findIndex将空视为undefined
object
属性简洁表示法 {a:a,b:b}=={a,b}, 方法简洁表示法 {a:a,b:b,say:function(){}}==={a,b,say(){}} 对象方法做构造函数方法不能简写 let obj={ fn(){ this.x=100 } } 属性名表达式 obj.a=10 标志符 obj["b"]=20方括号 let obj={ a:1, b:2 } 不要用对象做表达式 可枚举,enumerable 对象的每个属性都有个可描述对象,Object.getOwnPropertyDescriptor(obj,xy) Object.getOwnPropertyDescriptors(obj)方法,返回指定对象的所有自身属性(非继承的属性)的描述对象 enumerable为false,则不可被for in遍历 自身的和继承的 for..in循环:只遍历对象自身的和继承的可枚举属性 Object.keys():返回数组,包含对象自身所有可枚举属性的键名 JSON.stringify():只串行化对象自身的可枚举属性 Object.assign():忽略enumerable为false的属性,只拷贝对象自身的可枚举属性 Object.assign({},obj,变量1,变量2)合并对象,浅拷贝,返回新数组 Object.assign(obj,变量1,变量2)合并对象,浅拷贝 arr.concat()浅拷贝 Object.defineProperty 扩展属性 for..in循环:只遍历对象自身的和继承的可枚举属性 Object.keys(obj):返回数组,包含对象自身所有可枚举属性; Object.getOwnPropertyNames(objs)数组,包含自身所有属性和自身不可枚举属性的键名(非继承属性) Object.getOwnPropertySymbols(obj)数组,包含对象自身所有的Symbol属性的键名 Reflect.ownKeys(obj);返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是Symbol或字符串,也不管是否可枚举 首先遍历所有数值键,按照数值升序排列 其次遍历所有字符串键,按照加入时间升序排列 最后遍历所有的symbol键,按照加入时间升序排列
数据劫持
let obj={} Object.defineProperty(obj,"a",{ value:200,默认为undefined enumerable:true,默认为true writable:true,默认为false configurable:true;默认为false }) let obj={} Object.defineProperty(obj,"a",{ get:function(){ return this.value; }, set:function(v){ this.value=v; }, value:200,默认为undefined enumerable:true,默认为 configurable:true;默认为 }) Object.defineProperty(Array,"shuffle",{ value(arr){ return arr.sort(function(){ return 0.5-Math.random() }) } }) Object.defineProperty(Array,"shuffle",{ value:function(arr){ return arr.sort(function(){ return 0.5-Math.random() }) } }) console.log(Array.shuffle(arr))
super:指向当前对象的原型对象,只能在对象方法里使用 obj.__proto__读取或设置obj的原型方法 Object.prototype let obj=Object.create(xy)生成obj的原型对象为xy Object.setPrototypeOf(obj,mn)设置对象的原型对象 Object.getPrototypeOf(obj)获取对象的原型对象 扩展运算符...的解构赋值 将对象可遍历但没分配的属性拷贝过来 let{a,b...c}={a:1,b:2,m:3,n:4}; a=1,b=2,c={m:3,n:4} ...只能用在最后一个参数 ...后面不是对象强制转换成对象 ...后面是字符串时,转成类数组 扩展运算符的拷贝 let x={a:1,b:2} let obj={...x}等价于let obj=Object.assign({},x)//合并对象 只拿到了自身属性,获取自身和原型属性可用: let obj={ __proto__:Object.getPropertyOf(x), ...x } let obj=Object.assign(Object.create(Object.getPrototypeOf(x)),x) 扩展运算符...合并 let x={a:1,b:2}; let y={a:3,d:4}; let obj={...x,...y}等价于let obj=Object.assign({},x,y); 链判断运算符?. 链式调用时判断,如果是null和undefined直接返回undefined a?.b 属性调用 m?.[1] 索引调用 xiaoming.say?.() 方法调用 短路 delete 报错 括号 null判断?? 类似||,设定默认值 如果是null或undefined才生效 let title=a?.b?.title??"Null判断" 与&&或||一起用,必须括号 ??和| |效果类似,但是 区别在于??仅代表前面的表达式是null 或者undefined才会执行后面的表达式 。而| |则代表前面的表达式是null或者undefined或者false 或者0才会执行后面的。 Object.keys 返回键 Object.values 返回值 Object.entriess 返回键值对
参数默认值
es6之前设置参数默认值 a=a||100 es6的null判断运算符 a=a??100 es6函数参数默认值 function say(a=100){}默认值严格等于(===)undefined 函数里不能再次用let或const声明参数的同名变量 参数名不能重复 默认值不影响arguments 解构赋值做默认值 function abc({a,b=10}){ console.log(a,b) } abc()报错不能解构 abc({}undefined,10) function abc({a,b=10}={}){ console.log(a,b) } abc()undefined,10 abc({}undefined,10) 有默认值的参数不是最后一个参数,调用时不可省略 省略中间参数(5,null,10) function abc(a,b=5,c){ console.log(a,b,c)5 5 10 } abc(5,undefined,10) **长度** length计算方式:第一个默认值参数(不含)之前的参数个数 var a=100; function abc(a,b=function(){console.log(a=10)}){ a=25; b(); console.log(a)10 10 } abc(50) rest 参数用...abc形式表示剩余参数,abc为数组 代替了arguments rest不算入长度 rest必须放最后
严格模式
函数体内不能设置严格模式"use strict" 解决方案:全局设置,立即执行函数设置
箭头函数
let fn=x=>x 相当于:let fn=function(x){ return x; }
参数说明:1个参数可不加括号,无参数或多个函数要加括号 函数体说明:如果要return,无大括号可不写return 单行执行代码可省略{} 返回对象形式,必须放()里 特点:无arguments对象,用rest代替 this是创建时,不是调用时,箭头函数里没有this,this指向外层代码块不能做构造函数 箭头函数使用要注意什么 (1)函数体内的this指向不固定window,和父级作用域一致 (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 (3)不存在arguments对象,用rest代替该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。 (5) 没有prototype(原型)
(6) this指向不能被改变
set
支持forEach遍历 类似数组,每个值都是唯一的{1,2,3,5} let s=new Set(数组、字符串、NodeList、HTMLCollection、argument等) s.size 成员数 s.add(x)添加成员,存在则不添加,返回实例本身 s.delete(x)删除成员,返回true或false s.clear()清楚所有成员 s.has(n);是否有n set 用for of遍历 let s =new Set([1,2,3,4,5,6]); for(k of s.leys()){ console.log(k) }
数组去重
let s=new Set(1,2,2,3,4,3,4,5,2,1) console.log([...new Set(s)])
let x=new Set(1,3,2,4,5]),y=new Set([2,4,3,6,7]); 交集(公共部分)a,并集(合并,不重复)b,差集(没有对方的)c; let a=[...x].filter(v=>[...y].includes(v)); let b=new Set([...x,...y]); let c=[...x].filter(v=>![...y].includes(v)); weakSet 成员是对象或数组 没有size 不可遍历 add,has,delete 弱引用 let ws=new WeakSet([[1,2],[3,4]])
WeakSet和Set区别:
1、WeakSet解构和Set类似,都是不重复值的集合。 2、WeakSet的成员只能是对象,而不能是其他类型的值 3、WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用。也就是说,如果其他对象都不再引用该对象,那么垃圾垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象是否还存在于WeakSet之中。(WeakSet里面的引用都不计入垃圾回收机制,适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在WeakSet里面引用的就会自动消失。) WeakSet不能遍历,因为成员都是弱引用,随时可以能消失,遍历机制无法保证成员存在。 WeakSet的一个用处是储存DOM节点,而不担心这些节点从文档移除时会引发内存泄露。
Map
存键值对的对象或数组{"yes"=>true,"no"=>false} size, .set(key,value);key是任意值返回实例 .get(key) has(键) delete、clear map对象本身就是map.entries()
weakMap 类似Map属性必须是对象(引用类型) let wm=new weakMap([[{a:1},100,{b:2},200]]) Map Map转json 属性都是字符串 转成对象,JSON.stringify(obj),JSON.parse(str) 属性不全是字符串 转成数组,JSON.stringify([...map]),JSON.parse(str) JSON转Map 键名都是字符串 对象转Map 整个JSON是数组,且每个元素又是数组 new Map(JSON)
Proxy Reflect
Proxy是在访问之前做一层拦截,对访问进行处理 let proxy=new Proxy("拦截目标",{拦截行为}) 拦截目标:函数,数组,对象 拦截行为-get get(target,prop,proxy){ return 处理值 }
拦截属性读取
target:拦截目标 prop:属性 proxy:代理器对象 获取:proxy.getReceiver configurable:false并且writable:false,报错 let obj={a:1,b:2} let proxy=new Proxy(obj,{ get(t,p){ if(p in t){ 检测t里有没有p属性 return t[p] }else{ return `没有${p}属性` } } }) 获取对象属性,如果属性不存在,则打印 属性xx不存在 检测属性是否存在:if(prop in targer) if(Reflect.has(targer,prop)) 拦截行为set{ set(arguments,value,proxy){ target[prop]=value; } 拦截赋值操作 value:要修改的值 configurable:false(无法删除)并且writable:false (无法修改) 拦截行为apply apply(目标函数,this,目标参数实参列表){ return Reflect.apply(...arguments) } 拦截函数时调用,apply,call,bind() this:目标函数中的this Reflect.apply(...arguments)===Reflect.apply(目标函数,this,目标函数的实参列表) 拦截行为 has has(target,prop){ teturn prop in target; } 拦截in Reflect.has(target,prop) 拦截行为defineProperty defineProperty(target,prop,descriptor){ return } 拦截Object.defineProperty,让属性不可写,不能添加新属性 deleteProperty deletePrototypy(t,p){ return t[p]; } 拦截delete,返回false为属性不可删除 拦截行为getOwnPropertyDescriptor getOwnPropertyDescriptor(t,p){ return; } 拦截Object.getOwnPrototypeDescriptor,返回不能获取属性描述对象 拦截行为setPrototypeOf setPrototypeOf(target,proto){ return false; } 拦截Object(或Reflect).setPrototypeOf()设置原型效果(__proto__),return false为不可设置 取消代理 Proxy.revocable(obj,handler) 返回一个可取消的proxy实例,{proxy,revoke(){}};
Symbol
原始数据类型,表示独一无二的值 let s=Symbol()没有new ,不是对象,类似字符串 let s=Symbol("js")参数只是个描述,用于不同Symbol之间的区分,获取这个参数的描述:s.description Symbol可以转换字符串和布尔值 Symbol做对象属性 Symbol做对象属性不能用.访问 Symbol设置属性时要用[] 遍历对象Symbol属性 Object.getOwnPropertySymbols()获取所有Symbol的属性 Reflect.ownKeys()获取自身的属性(包含不可枚举的属性,包含Symbol) Symbol.for(),Symbol.keyFor() Symbol()!==Symbol() Symbol.for()生成新的Symbol,可搜索,for登记 使用Symbol.for(参数)先检查有没有相同参数的Symbol,有则返回没有则创建新的Symbol Symbol.for()===Symbol.for() Symbol.keyFor(s)返回登记的Symbol的key(参数)只返回Symbol.for()创建的
Iterator
遍历器是一种接口,为不同数据提供一种统一的访问机制,任何部署了iterator接口的数据都可以用for..of遍历 执行过程 创建指针对象,指向数据的起始位置 第一次调用next时,指向第一个成员 第二次调用next时指向第二个成员 一直到数据结束位置
function abc(arr){ var n=0; return { next(){ return n<arr.length?{value:arr[n++],done:false}:{value:undefined,done:true}; } } } let x=abc([2,4,5,8]) next返回成员信息 value 返回成员的值 done遍历是否结束
```javascript Symbol.iterator属性 默认的interator接口部署在Symbol的iterator属性上,Symbol.iterator是遍历器的生成函数,执行这个函数就会返回一个遍历器 Symbol.iterator是表达式,返回Symbol对象的iterator属性(类型Symbol),所以要用[]设置或访问 原生数据具有iterator接口 数组,字符串,map,set,HTMLCollection,NodeList。类数组等 let str="javascript"; let iterator=str[Symbol.iterator](); iterator.next() 普通对象设置iterator接口 obj[Symbol.iterator]=function(){} [...obj] let obj={a:1,b:2}; obj[Symbol.iterator]=function(){ obj没有length,取obj的key let keys=Object.keys(obj); keys["a","b"] let index=0; return { next(){ return index<keys.length?{value:[keys[index],obj[keys[index++]]],done:false}:{done:true} } } } for(let i of obj){ console.log(i) }
gennertor 异步同步化
ajax回调,异步用同步表示 回调地狱 $(function(){ $.ajax({url:"1.php",success:(data)=>{ var n=data; console.log(n) $.ajax({url:`${n}.php`,success:(data1)=>{ var n1=data1; console.log(n1) $.ajax({url:`${n1}.php`,success:(data2)=>{ console.log(data2) }}) }}) } }) })
解决回调地狱问题
generator:异步问题同步显示 function abc(){ let a=ajax("1.txt"); let b=ajax(a+".txt"); let c=ajax(b+".txt"); console.log(c) } function ajax(url,callback){ let xhr.new XMLHttpRequest("get",url); xhr.send() xhr.onreadystatechange=function(){ if(xhr.readyState==4){ if(xhr.status==200){ callback(xhr.responseText); } } } } function qs(url){ ajax(url,function(dt){ gt.next(dt) }) } function* abc(){ let a=yield qs(1.txt); document.getElementByClassName("res").innnerHTML+=a; let b=yield qs(b+".txt") document.getElementByClassName("res").innnerHTML+=b; let c=yield qs(c+".txt") document.getElementByClassName("res").innnerHTML+=c; console.log(c) } let gt=abc() promise: ajax("1.txt").then(()=>{return ajax("2.txt")}).then().then()
promise
异步编程解决方案 保存着未来才会结束的事件结果 特点: 1.状态不受外界影响,3个状态,pending进行,fulfilled已成功,rejected已失败,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态 2.状态改变:pending->fulfilled,pending->rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved promise基本用法
let promise=new Promise((resolve,reject)=>{ if(异步操作成功){ resolve(value);padding->resolve未完成到成功,异步操作成功调用,将值(value)做参数传递出去 }else{ reject(error);pending->rejected未完成到失败,将异步操作的错误传递出去 } })
promise的then promise对象的then方法分别指定resolved和rejected回调函数
promise.then(function(value){ 成功的操作 },function(error){ 失败的操作 })
function abc(ms){ return new Promise((resolve,reject)=>{ setTimeout(resolve,ms,"aaaa"); setTimeout(reject,5000,"错误") throw new Error("错误") }) } abc(1000).then((value)=>console.log(value),(txt)=>console.log(txt))
throw 抛出错误 throw new Error("错误")
try{尝试 console.log(a) }catch(err){捕捉 console.log(err) } finally{最终 console.log(end) }
promise立即执行 实例中直接调用resolve或rejected方法
function abc(){ return new Promise((resolve,reject)=>{ console.log(3); resolve() }) } abc.then(function(){ console.log(1) }) console.log(2)
promise封装图片加载
function loadImage(url){ return new Promise((resolve,reject)=>{ var img=new Image(); img.src=url; img.onload=function(){ resolve(this); } img.onerror=function(){ reject() } }) } loadImage(url).then((img)=>{ document.body.insertBefore(img,document.body.firstChild), ()=>{ console.log("加载失败") } })
promise封装getJson方法
function getJson(url){ return new Promise((resolve,reject)=>{ let xhr=new XMLHttpRequest(); xhr.open("get",url); xhr.responseType="json"; xhr.setRequestHeader("Accept","application/json"); xhr.send(); xhr.onreadystatechange=function(){ if(xhr.readyState!=4){ return; }else{ if(xhr.status==200){ resolve(xhr.reponse) }else{ reject(xhr.statusText) } } } }) } getJson(文件名.json).then((data)=>{console.log(data)},(txt)=>{console.log(txt)})
promise嵌套
一个resolve用另一个promise做参数 let a=new Promise((rv,rj)=>{ setTimeout(()=>{rv()},5000) }).then(()=>console.log(5000)) let b=new Promise((rv,rj)=>{ setTimeout(()=>{rv(a)},1000) }).then(()=>console.log(1000)) return resolve,reject不中断后面语句执行,想要阻止用return resolve() then then有两个参数,接收promise的resolve和reject then里的resolve返回结果,作为下一个then的resolve参数(链式调用) Promise.prototype.catch catch接收错误(promise错误,throwError,then错误) catch接收前面所有的错误,不接收后面错误,无错误则跳过 不推荐reject,推荐catch .then().catch(()=>{}) finally(()=>{})
通常放在最后面,finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作