2022年6月22日,第123届ECMA大会通过ECMAScript2022语言规范,正式成为标准。一起来看看ECMAScript2022的新特性吧!新特性概述顶层AwaitObject.hasOwn()at()error.cause正则表达式匹配索引类ES14:Array.prototype.findLast和Array.prototype.findLastIndex的提案。ES2017(ES8)中引入了顶层Await(topawait)async和await来简化Promise操作,但是有个问题就是await只能在async内部使用,当我们直接在最外层使用await时会报错报告:`UncaughtSyntaxError:awaitisonlyvalidinasyncfunctionsandthetoplevelbodiesofmodules`在没有顶级等待之前,当我们导入外部promise.js文件时,因为我们需要等待执行外部js在执行其他操作之前完成//promise.jsletres={name:""},num;constnp=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>{resolve(456);},100);});};constp=async()=>{constres1=awaitnp();res.name=res1;num=res1;};p();exportdefaultres;//validate.jsimportresfrom"./p.js";console.log("res",res,num);//此时,两个res和num是未定义的,因为异步执行完成后需要访问res和num,所以我们可以加一个定时器来解决setTimeout(()=>{console.log("res3000",res,num);},1000);//res可以正确输出{name:456}//num还是undefined为什么res可以正常输出,而num不能?这是因为res是一个对象,是一个引用类型。100毫秒后,异步操作执行完成并赋值,导出的res和p.js中的res指向同一个地址address,所以可以监听变化,但是num是基本数据类型,导出的和p.js中的不一样,所以监听不到,所以一直都是undefined,而在实际项目中,异步时间不确定,所以这个方法存在一定的缺陷,此时可以使用顶层await来实现//p.jsletres={name:""},num;constnp=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>{resolve(456);},100);});};//在这里转换constres1=awaitnp();res.name=res1;num=res1;export{res,num};//validate.jsimport{res,num}from"./p.js";console.log("resadnnum",res,num);//所有正常输出的代码从上到下执行,遇到await和进入waiting,np函数会在执行完成后赋值,赋值后导出完成了await使用一些场景资源进行初始化:比如等待一个文件(图片,js(初始化变量的js)等)加载完成再渲染依赖fallback:letdepVersion;try{depVersion=awaitimport(xxx/depVersion-2.0.0.min.js)}catch{depVersion=awaitimport(xxx/depVersion-1.5.0.min.js)}模块动态加载:letmyModule='await-module'constmodule=awaitimport(`./${myModule}`)兼容性:Object.hasOwn()ES5:当我们检查一个属性属于一个对象时可以使用常见的例子:object={firstName:'four',lastName:'li'}for(constkeyinobject){if(Object.hasOwnProperty.call(object,key)){constelement=object[key];console.log(element)}}ES6:Object.hasOwn属性是检查属性是否更清晰、更可靠的方法直接在对象上设置方法的常见示例:object={firstName:'four',lastName:'Li'}for(constkeyinobject){if(Object.hasOwn(object,key)){constelement=object[key];console.log(element)}}at()一个TC39提案,将.at()方法添加到所有基本可索引类(Array、String、TypedArray)ES13之前,要从可索引对象的末尾访问值,通常的做法是写arr[arr.length-N]或使用arr.slice(-N)[0]ES13:你可以使用at()方法//arrayconstarray=[0,1,2,3,4,5];array.at(-1)//5array.at(-2)//4//stringstring='abcdefg'string.at(-2)//fErrorCause有时,代码块中的错误需要根据其原因进行不同的处理,但错误的原因都比较相似(例如:错误类型和消息相同)//在ES13之前,错误通常是通过以下方式处理的',err.message);//第二种需要连接错误信息constwrapErr=newError('Downloadrawresourcefailed');//创建一个cause属性来接收错误上下文wrapErr.cause='Errorreason:'+err;throwwrapErr;//第三种需要连接错误信息classCustomErrorextendsError{constructor(msg,cause){super(msg);//创建一个cause属性来接收错误上下文this.cause=cause;}}thrownewCustomError('下载原始资源失败',err);});}try{constres=awaiterrFunc()}catch(err){console.log(err)console.log(err.cause)}//第一个输出:UncaughtError:Myerrormessage:Failedtofetch//第一个输出:undefined//第二个输出:UncaughtError:Myerrormessage//第二个输出:ErrorReason:err//第三种:UncaughtError:Myerrormessage//第三种输出:Errorreason:err正则表达式匹配索引正则表达式加上修饰符d会生成一个匹配对象,记录每组的开始和捕获的结束索引,由于/d标志的存在,m1也有一个属性.indices,用于记录捕获到的每个数字组//?n:命名组,m为组名,n为正则表达式constre1=/a+(?z)?/d;//indicesarerelativetothestartoftheinputstring:consts1="xaaaz";constm1=re1.exec(s1);console.log(m1.indices)进行字符串切割时,可以通过修饰符d匹配执行字符切割的索引范围//使用s1.slice(...m1.indices[0])//aaaz//等同于s1.slice(1,5)//aaaz//按组切割s1。slice(...m1.indices.groups['Z'])//类z的公共实例字段在ES13之前。在定义类的属性时,需要在构造函数中定义实例字段和绑定方法classmyClass{constructor(){this.count=1this.increment=this.increment.bind(this);}increment(){this.count+=1}}ES13可以使用public实例字段,简化类定义,让代码更加简洁易读classmyClass{count=1increment=()=>{this.count+=1}}privateinstancefields默认情况下,一个类中的所有属性都是public的,类之间可以在类外修改,例如classmyClass{count=1setCount=()=>{this.count+=1}}constes13=newmyClass()es13.count=5//myClass{count:5,setCount:?}count:5setCount:()=>{this.count+=1}[[Prototype]]:Object可以看出从上面的例子来看,当我们直接设置co设置unt属性时,直接跳过setCount进行设置。有时我们不想这样做,所以我们可以使用私有实例字段。用法非常简单。只需要在私有字段中添加#即可实现。当然,在调用的时候我们也要加上#来调用,如下:classmyClass{#count=1setCount=()=>{this.#count+=1}}constes13=newmyClass()es13.setCount()//正常修改,执行一次setCount方法后,每次#count的值都会加1//直接修改私有属性es13.#count=5//error:UncaughtSyntaxError:Privatefield'#count'mustbedeclaredinanenclosingclass可以看到,当我们直接修改私有属性时,浏览器直接抛出错误:UncaughtSyntaxError:Privatefield'#count'mustbedeclaredinanenclosingclass。私有方法都有私有属性。私有方法怎么能少呢?只需将#添加到方法和属性中:classmyClass{#count=1#setCount=()=>{this.#count+=1}newSetCount=()=>{this.#setCount()}}constes13=newmyClass()es13.#setCount()//直接调用私有方法报错:UncaughtSyntaxError:Privatefield'#setCount'mustbedeclaredinanenclosingclass//通过公共方法newSetCount调用es13.newSetCount()//VM436:9UncaughtTypeError:无法从类未声明为静态的对象中读取私有成员#setCount公共字段、静态私有字段、静态私有方法与私有实例字段和方法一样,静态私有字段和方法也使用散列#前缀来定义classmyClass{//静态公共字段staticcolor='blue'//静态私有字段static#count=1//静态私有方法static#setCount=()=>{this.#count+=1}newSetCount=()=>{this.#setCount()}}constes13=newmyClass()instanceOnes13,只有newSetCount()方法es13.newSetCount()//Error:UncaughtSyntaxError:Privatefield'#setCount'mustbedeclaredinanenclosingclass私有静态字段有一个限制:只有定义了私有静态字段的类才能访问thisfield这个在使用this的时候可能会出现意想不到的情况,所以我们需要改成classmyClass{//staticprivatefieldstatic#count=1//staticprivatemethodstatic#setCount=()=>{//实例化之后,this不再指向myClass,全部需要改为myClass类调用myClass.#count+=1}newSetCount=()=>{//实例化后this不再指向myClass,全部需要改为myClass类调用myClass。#setCount()}}constes13=newmyClass()es13.newSetCount()//Successclassstaticblock以前,如果我们想在初始化期间像try...catch这样的异常处理,我们必须在类逻辑之外编写它。该规范提供了一种在类声明/定义期间评估静态初始化代码块的优雅方法,可以访问类的私有字段。在ES13类Person之前{staticEEEOR="error"staticSUCCESS_TYPE="success_type";constructor(){//...}try{//...}catch{//...}}上面代码直接报错:UncaughtSyntaxError:Unexpectedtoken'{'ES13:Justwraptry..静态类Person中的.cathc{staticEEEOR="error"staticSUCCESS_TYPE="success_type";constructor(){//...}static{try{//...}catch{//...}}}ES14新提案Array.prototype.findLastArray.prototype.findLastIndexTips:Array.prototype.findLast和Array.prototype.find行为相同,但从最后一次迭代变为第一次迭代。Array.prototype.findLastIndex的行为与Array.prototype.findIndex相同,但从最后一个迭代到第一个。constarray=[{value:1},{value:2},{value:3},{value:4}];array.find(n=>n.value%2===1);//{值:1}array.findIndex(n=>n.value%2===1);//0//========在提议之前===========//find[...array].reverse().find(n=>n.value%2===1);//{value:3}//findIndexarray.length-1-[...array].reverse().findIndex(n=>n.value%2===1);//2array.length-1-[...array].reverse().findIndex(n=>n.value===42);//应该是-1,但是4//========在提案中===========//findarray.findLast(n=>n.value%2===1);//{value:3}//findIndexarray.findLastIndex(n=>n.value%2===1);//2array.findLastIndex(n=>n.value===42);//-1