智慧印刷工坊

智慧印刷工坊

JS一网打尽:从ES6到ES10的新特性万字大总结(典藏查阅)

admin 35 169
介绍

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)在标准ECMA-262中定义的脚本语言规范。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但实际上后两者是ECMA-262标准的实现和扩展。


历史版本

至发稿日为止有九个ECMA-262版本发表。其历史版本如下:

1997年6月:第一版

1998年6月:修改格式,使其与ISO/IEC16262国际标准一样

1999年12月:强大的正则表达式,更好的词法作用域链处理,新的控制指令,异常处理,错误定义更加明确,数据输出的格式化及其它改变

2009年12月:添加严格模式("usestrict")。修改了前面版本模糊不清的概念。增加了getters,setters,JSON以及在对象属性上更完整的反射。

2011年6月:ECMAScript标5.1版形式上完全一致于国际标准ISO/IEC16262:2011。

2015年6月:ECMAScript2015(ES2015),第6版,最早被称作是ECMAScript6(ES6),添加了类和模块的语法,其他特性包括迭代器,Python风格的生成器和生成器表达式,箭头函数,二进制数据,静态类型数组,集合(maps,sets和weakmaps),promise,reflection和proxies。作为最早的ECMAScriptHarmony版本,也被叫做ES6Harmony。

2016年6月:ECMAScript2016(ES2016),第7版,多个新的概念和语言特性。

2017年6月:ECMAScript2017(ES2017),第8版,多个新的概念和语言特性。

2018年6月:ECMAScript2018(ES2018),第9版,包含了异步循环,生成器,新的正则表达式特性和rest/spread语法。

2019年6月:ECMAScript2019(ES2019),第10版。


发展标准

TC39(TechnicalCommittee39)是一个推动JavaScript发展的委员会,它的成语来自各个主流浏览器的代表成语。会议实行多数决,每一项决策只有大部分人同意且没有强烈反对才能去实现。

TC39成员制定着ECMAScript的未来。

每一项新特性最终要进入到ECMAScript规范里,需要经历5个阶段,这5个阶段如下:

Stage0:Strawperson只要是TC39成员或者贡献者,都可以提交想法

Stage1:Proposal这个阶段确定一个正式的提案

Stage2:draft规范的第一个版本,进入此阶段的提案大概率会成为标准

Stage3:Candidate进一步完善提案细则

Stage4:Finished表示已准备好将其添加到正式的ECMAScript标准中

由于ES6以前的属性诞生年底久远,我们使用也比较普遍,遂不进行说明,ES6之后的语言风格跟ES5以前的差异比较大,所以单独拎出来做个记录。

ES6(ES2015)Let和Const

在ES6以前,JS只有var一种声明方式,但是在ES6之后,就多了let跟const这两种方式。用var定义的变量没有块级作用域的概念,而let跟const则会有,因为这三个关键字创建是不一样的。

区别如下:

{vara=10letb=20constc=30}a//10b//UncaughtReferenceError:bisnotdefinedc//cisnotdefinedletd=40conste=50d=60d//60e=70//VM231:1UncaughtTypeError:Assignmenttoconstantvariable.


类(Class)

在ES6之前,如果我们要生成一个实例对象,传统的方法就是写一个构造函数,例子如下:

functionPerson(name,age){==age}=function(){return'Mynameis'++',Iam'+}

但是在ES6之后,我们只需要写成以下形式:

classPerson{constructor(name,age){==age}information(){return'Mynameis'++',Iam'+}}
箭头函数(Arrowfunction)

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。

在ES6以前,我们写函数一般是:

varlist=[1,2,3,4,5,6,7]varnewList=(function(item){returnitem*item})

但是在ES6里,我们可以:

constlist=[1,2,3,4,5,6,7]constnewList=(item=item*item)

看,是不是简洁了不少

函数参数默认值(Functionparameterdefaults)

在ES6之前,如果我们写函数需要定义初始值的时候,需要这么写:

functionconfig(data){vardata=data||'dataisempty'}

这样看起来也没有问题,但是如果参数的布尔值为falsy时就会出问题,例如我们这样调用config:

config(0)config('')

那么结果就永远是后面的值

如果我们用函数参数默认值就没有这个问题,写法如下:

constconfig=(data='dataisempty')={}
模板字符串(Templatestring)

在ES6之前,如果我们要拼接字符串,则需要像这样:

varname='kris'varage=24varinfo='Mynameis'++',Iam'+

但是在ES6之后,我们只需要写成以下形式:

constname='kris'constage=24constinfo=`Mynameis${name},Iam${age}`
解构赋值(Destructuringassignment)

我们通过解构赋值,可以将属性/值从对象/数组中取出,赋值给其他变量。

比如我们需要交换两个变量的值,在ES6之前我们可能需要:

vara=10varb=20vartemp=aa=bb=temp

但是在ES6里,我们有:

leta=10letb=20[a,b]=[b,a]

是不是方便很多

模块化(Module)

在ES6之前,JS并没有模块化的概念,有的也只是社区定制的类似CommonJS和AMD之类的规则。例如基于CommonJS的NodeJS:

////输出const{PI}==(r)=PI*r**2=(r)=2*PI*r////输入constcircle=require('./')(`半径为4的圆的面积是${(4)}`)

在ES6之后我们则可以写成以下形式:

////输出const{PI}=Mathexportconstarea=(r)=PI*r**2exportconstcircumference=(r)=2*PI*r////输入import{area}='./'(`半径为4的圆的面积是:${area(4)}`)
扩展操作符(Spreadoperator)

扩展操作符可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开;还可以在构造字面量对象时,将对象表达式按key-value的方式展开。

比如在ES5的时候,我们要对一个数组的元素进行相加,在不使用reduce或者reduceRight的场合,我们需要:

functionsum(x,y,z){returnx+y+z;}varlist=[5,6,7]vartotal=(null,list)

但是如果我们使用扩展操作符,只需要如下:

constsum=(x,y,z)=x+y+zconstlist=[5,6,7]consttotal=sum(list)

非常的简单,但是要注意的是扩展操作符只能用于可迭代对象

如果是下面的情况,是会报错的:

varobj={'key1':'value1'}vararray=[obj]//TypeError:objisnotiterable
对象属性简写(Objectattributeshorthand)

在ES6之前,如果我们要将某个变量赋值为同样名称的对象元素,则需要:

varcat='Miaow'vardog='Woof'varbird='Peetpeet'varsomeObject={cat:cat,dog:dog,bird:bird}

但是在ES6里我们就方便很多:

letcat='Miaow'letdog='Woof'letbird='Peetpeet'letsomeObject={cat,dog,bird}(someObject)//{//cat:"Miaow",//dog:"Woof",//bird:"Peetpeet"//}

非常方便

Promise

Promise是ES6提供的一种异步解决方案,比回调函数更加清晰明了。

Promise翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,分别是:

等待中(ping)

完成了(resolved)

拒绝了(rejected)

这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为resolved后,就不能再次改变

newPromise((resolve,reject)={resolve('success')//无效reject('reject')})

当我们在构造Promise的时候,构造函数内部的代码是立即执行的

newPromise((resolve,reject)={('newPromise')resolve('success')})('finifsh')//newPromise-finifsh

Promise实现了链式调用,也就是说每次调用then之后返回的都是一个Promise,并且是一个全新的Promise,原因也是因为状态不可变。如果你在then中使用了return,那么return的值会被()包装

(1).then(res={(res)//=1return2//包装成(2)}).then(res={(res)//=2})

当然了,Promise也很好地解决了回调地狱的问题,例如:

ajax(url,()={//处理逻辑ajax(url1,()={//处理逻辑ajax(url2,()={//处理逻辑})})})

可以改写成:

ajax(url).then(res={(res)returnajax(url1)}).then(res={(res)returnajax(url2)}).then(res=(res))
forof

forof语句在可迭代对象(包括Array,Map,Set,String,TypedArray,arguments对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

例子如下:

constarray1=['a','b','c'];for(constelementofarray1){(element)}//"a"//"b"//"c"
Symbol

symbol是一种基本数据类型,Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"newSymbol()"。

每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。

例子如下:

constsymbol1=Symbol();constsymbol2=Symbol(42);constsymbol3=Symbol('foo');(typeofsymbol1);//"symbol"(());//"Symbol(foo)"(Symbol('foo')===Symbol('foo'));//false
迭代器(Iterator)/生成器(Generator)

迭代器(Iterator)是一种迭代的机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要内部有Iterator接口,就可以完成依次迭代操作。

一旦创建,迭代器对象可以通过重复调用next()显式地迭代,从而获取该对象每一级的值,直到迭代完,返回{value:undefined,done:true}

虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数,同时它可以自动维护自己的状态。生成器函数使用function*语法编写。最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。

可以根据需要多次调用该函数,并且每次都返回一个新的Generator,但每个Generator只能迭代一次。

所以我们可以有以下例子:

function*makeRangeIterator(start=0,=Infinity,step=1){for(leti=start;i;i+=step){yieldi;}}vara=makeRangeIterator(1,10,2)()//{value:1,done:false}()//{value:3,done:false}()//{value:5,done:false}()//{value:7,done:false}()//{value:9,done:false}()//{value:undefined,done:true}
Set/WeakSet

Set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

所以我们可以通过Set实现数组去重

constnumbers=[2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]([newSet(numbers)])//[2,3,4,5,6,7,32]

WeakSet结构与Set类似,但区别有以下两点:

WeakSet对象中只能存放对象引用,不能存放值,而Set对象都可以。

WeakSet对象中存储的对象值都是被弱引用的,如果没有其他的变量或属性引用这个对象值,则这个对象值会被当成垃圾回收掉.正因为这样,WeakSet对象是无法被枚举的,没有办法拿到它包含的所有元素。

所以代码如下:

varws=newWeakSet()varobj={}varfoo={}(window)(obj)(window)//(foo)//false,对象foo并没有被添加进ws中(window)//从集合中删除window对象(window)//false,window对象已经被删除了()//清空整个WeakSet对象
Map/WeakMap

Map对象保存键值对。任何值(对象或者原始值)都可以作为一个键或一个值。

例子如下,我们甚至可以使用NaN来作为键值:

varmyMap=newMap();(NaN,"notanumber");(NaN);//"notanumber"varotherNaN=Number("foo");(otherNaN);//"notanumber"

WeakMap对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

跟Map的区别与Set跟WeakSet的区别相似,具体代码如下:

varwm1=newWeakMap(),wm2=newWeakMap(),wm3=newWeakMap();varo1={},o2=function(){},o3=window;(o1,37);(o2,"azerty");(o1,o2);//value可以是任意值,包括一个对象(o3,undefined);(wm1,wm2);//键和值可以是任意对象,甚至另外一个WeakMap对象(o2);//"azerty"(o2);//undefined,wm2中没有o2这个键(o3);//undefined,值就是(o2);//(o2);//(o3);//true(即使值是undefined)(o1,37);(o1);//37();(o1);//undefined,wm3已被清空(o1);//(o1);(o1);//false
Proxy/Reflect

Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

Reflect是一个内置的对象,它提供拦截JavaScript操作的方法。这些方法与Proxy的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

Proxy跟Reflect是非常完美的配合,例子如下:

constobserve=(data,callback)={returnnewProxy(data,{get(target,key){(target,key)},set(target,key,value,proxy){callback(key,value);target[key]=value;(target,key,value,proxy)}})}constFooBar={open:false};constFooBarObserver=observe(FooBar,(property,value)={property==='open'value?('FooBarisopen!!!'):('keepwaiting');});()//=true//FooBarisopen!!!

当然也不是什么都可以被代理的,如果对象带有configurable:false跟writable:false属性,则代理失效。

Regex对象的扩展正则新增符号

i修饰符//i修饰符/[a-z]/('\u212A')//false/[a-z]/('\u212A')//true

y修饰符//y修饰符vars='aaa_aa_a';varr1=/a+/g;varr2=/a+/y;(s)//["aaa"](s)//["aaa"](s)//["aa"](s)//null

//查看RegExp构造函数的修饰符varregex=newRegExp('xyz','i')//'i'

unicode模式vars=''/^.$/.test(s)//false/^.$/(s)//true

u转义//u转义/\,////\,//\,/u//报错没有u修饰符时,逗号前面的反斜杠是无效的,加了u修饰符就报错。

引用constRE_TWICE=/^(?word[a-z]+)!\kword$/;RE_('abc!abc')//trueRE_('abc!ab')//falseconstRE_TWICE=/^(?word[a-z]+)!\1$/;RE_('abc!abc')//trueRE_('abc!ab')//false

字符串方法的实现改为调用RegExp方法

调用[]

调用[]

调用[]

调用[]

正则新增属性

表示是否有y修饰符/hello\d///true

获取修饰符/abc///'gi'

Math对象的扩展

二进制表示法:0b或0B开头表示二进制(0bXX或0BXX)

二进制表示法:0b或0B开头表示二进制(0bXX或0BXX)

八进制表示法:0o或0O开头表示二进制(0oXX或0OXX)

:数值最小精度

_SAFE_INTEGER:最小安全数值(-2^53)

_SAFE_INTEGER:最大安全数值(2^53)

():返回转换值的整数部分

():返回转换值的浮点数部分

():是否为有限数值

():是否为NaN

():是否为整数

():是否在数值安全范围内

():返回数值整数部分

():返回数值类型(正数1、负数-1、零0)

():返回数值立方根

():返回数值的32位无符号整数形式

():返回两个数值相乘

():返回数值的32位单精度浮点数形式

():返回所有数值平方和的平方根

():返回e^n-1

():返回1+n的自然对数((1+n))

():返回以10为底的n的对数

():返回以2为底的n的对数

():返回n的双曲正弦

():返回n的双曲余弦

():返回n的双曲正切

():返回n的反双曲正弦

():返回n的反双曲余弦

():返回n的反双曲正切

Array对象的扩展

:转换具有Iterator接口的数据结构为真正数组,返回新数组。(('foo'))//["f","o","o"](([1,2,3],x=x+x))//[2,4,6]

():转换一组值为真正数组,返回新数组。(7)//[7](1,2,3)//[1,2,3]Array(7)//[empty,empty,empty,empty,empty,empty]Array(1,2,3)//[1,2,3]

():把指定位置的成员复制到其他位置,返回原数组constarray1=['a','b','c','d','e']((0,3,4))//["d","b","c","d","e"]((1,3))//["d","d","e","d","e"]

():返回第一个符合条件的成员constarray1=[5,12,8,130,44]constfound=(element=element10)(found)//12

():返回第一个符合条件的成员索引值constarray1=[5,12,8,130,44]constisLargeNumber=(element)=((isLargeNumber))//3

():根据指定值填充整个数组,返回原数组constarray1=[1,2,3,4]((0,2,4))//[1,2,0,0]((5,1))//[1,5,5,5]((6))//[6,6,6,6]

():返回以索引值为遍历器的对象constarray1=['a','b','c']constiterator=()for(constkeyofiterator){(key)}//0//1//2

():返回以属性值为遍历器的对象constarray1=['a','b','c']constiterator=()for(constkeyofiterator){(key)}//a//b//c

():返回以索引值和属性值为遍历器的对象constarray1=['a','b','c']constiterator=()(().value)//[0,"a"](().value)//[1,"b"]

数组空位:ES6明确将数组空位转为undefined或者(['a',,'b'])//["a",undefined,"b"][['a',,'b']]//["a",undefined,"b"]Array(3)//[empty×3][,'a']//[empty,"a"]

ES7(ES2016)()

includes()方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回true,否则返回false。

代码如下:

constarray1=[1,2,3]((2))//trueconstpets=['cat','dog','bat'](('cat'))//(('at'))//false
幂运算符**

幂运算符**,具有与()一样的功能,代码如下:

(2**10)//1024((2,10))//1024
模板字符串(Templatestring)

自ES7起,带标签的模版字面量遵守以下转义序列的规则:

Unicode字符以"u"开头,例如\u00A9

Unicode码位用"u{}"表示,例如\u{2F804}

十六进制以"x"开头,例如\xA9

八进制以""和数字开头,例如\251

这表示类似下面这种带标签的模版是有问题的,因为对于每一个ECMAScript语法,解析器都会去查找有效的转义序列,但是只能得到这是一个形式错误的语法:

latex`\unicode`//在较老的ECMAScript版本中报错(ES2016及更早)//SyntaxError:malformedUnicodecharacterescapesequence
ES8(ES2017)async/await

虽然Promise可以解决回调地狱的问题,但是链式调用太多,则会变成另一种形式的回调地狱——面条地狱,所以在ES8里则出现了Promise的语法糖async/await,专门解决这个问题。

我们先看一下下面的Promise代码:

fetch('').then(response=()).then(myBlob={letobjectURL=(myBlob)letimage=('img')=(image)}).catch(e={('Therehasbeenaproblemwithyourfetchoperation:'+)})

然后再看看async/await版的,这样看起来是不是更清晰了。

asyncfunctionmyFetch(){letresponse=awaitfetch('')letmyBlob=()letobjectURL=(myBlob)letimage=('img')=(image)}myFetch()

当然,如果你喜欢,你甚至可以两者混用

asyncfunctionmyFetch(){letresponse=awaitfetch('')()}myFetch().then((blob)={letobjectURL=(blob)letimage=('img')=(image)})
()

()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用forin循环的顺序相同(区别在于for-in循环枚举原型链中的属性)。

代码如下:

constobject1={a:'somestring',b:42,c:false}((object1))//["somestring",42,false]
()

()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用forin循环遍历该对象时返回的顺序一致(区别在于for-in循环还会枚举原型链中的属性)。

代码如下:

constobject1={a:'somestring',b:42}for(let[key,value](object1)){(`${key}:${value}`)}//"a:somestring"//"b:42"
padStart()

padStart()方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。

代码如下:

conststr1='5'((2,'0'))//"05"constfullNumber='2034399002125581'constlast4Digits=(-4)constmaskedNumber=(,'*')(maskedNumber)//"************5581"
pad()

pad()方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。

conststr1='BreadedMushrooms'((25,'.'))//"BreadedMushrooms.."conststr2='200'((5))//"200"
函数参数结尾逗号(Functionparameterlistsandcallstrailingcommas)

在ES5里就添加了对象的尾逗号,不过并不支持函数参数,但是在ES8之后,便开始支持这一特性,代码如下:

//参数定义functionf(p){}functionf(p,){}(p)={}(p,)={}classC{one(a,){},two(a,b,){},}varobj={one(a,){},two(a,b,){},};//函数调用f(p)f(p,)(10,20)(10,20,)

但是以下的方式是不合法的:

仅仅包含逗号的函数参数定义或者函数调用会抛出SyntaxError。而且,当使用剩余参数的时候,并不支持尾后逗号,例子如下:

functionf(,){}//SyntaxError:missingformalparameter(,)={}//SyntaxError:expectedexpression,got','f(,)//SyntaxError:expectedexpression,got','functionf(p,){}//SyntaxError:parameterafterrestparameter(p,)={}//SyntaxError:expectedclosingparenthesis,got','

在解构里也可以使用,代码如下:

//带有尾后逗号的数组解构[a,b,]=[1,2]//带有尾后逗号的对象解构varo={p:42,q:true,}var{p,q,}=o

同样地,在使用剩余参数时,会抛出SyntaxError,代码如下:

var[a,b,]=[1,2,3]//SyntaxError:restelementmaynothaveatrailingcomma
ShareArrayBuffer(因安全问题,暂时在Chrome跟FireFox中被禁用)

SharedArrayBuffer对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于ArrayBuffer对象,它们都可以用来在共享内存(sharedmemory)上创建视图。与ArrayBuffer不同的是,SharedArrayBuffer不能被分离。

代码如下:

letsab=newSharedArrayBuffer(1024)//必须实例化(sab)
Atomics对象

Atomics对象提供了一组静态方法用来对SharedArrayBuffer对象进行原子操作。

方法如下:

():将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。

():将指定位置上的数组元素与给定的值相与,并返回与操作前该元素的值。

():如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。

():将数组中指定的元素更新为给定的值,并返回该元素更新前的值。

():返回数组中指定元素的值。

():将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值。

():将数组中指定的元素设置为给定的值,并返回该值。

():将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。

():将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值。

():检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为"ok"、"not-equal"或"time-out"。调用时,如果当前线程不允许阻塞,则会抛出异常(大多数浏览器都不允许在主线程中调用wait())。

():唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。

(size):可以用来检测当前系统是否支持硬件级的原子操作。对于指定大小的数组,如果当前系统支持硬件级的原子操作,则返回true;否则就意味着对于该数组,Atomics对象中的各原子操作都只能用锁来实现。此函数面向的是技术专家。

()

()方法用来获取一个对象的所有自身属性的描述符。代码如下:

constobject1={property1:42}constdescriptors1=(object1)()//()//42//浅拷贝一个对象((obj),(obj))//创建子类functionsuperclass(){}={//在这里定义方法和属性}functionsubclass(){}=(,({//在这里定义方法和属性}))
ES9(ES2018)forawaitof

forawaitof语句会在异步或者同步可迭代对象上创建一个迭代循环,包括String,Array,Array-like对象(比如arguments或者NodeList),TypedArray,Map,Set和自定义的异步或者同步可迭代对象。其会调用自定义迭代钩子,并为每个不同属性的值执行语句。

配合迭代异步生成器,例子如下:

asyncfunction*asyncGenerator(){vari=0while(i3){yieldi++}}(asyncfunction(){forawait(numofasyncGenerator()){(num)}})()//0//1//2
模板字符串(Templatestring)

ES9开始,模板字符串允许嵌套支持常见转义序列,移除对ECMAScript在带标签的模版字符串中转义序列的语法限制。

不过,非法转义序列在"cooked"当中仍然会体现出来。它们将以undefined元素的形式存在于"cooked"之中,代码如下:

functionlatex(str){return{"cooked":str[0],"raw":[0]}}latex`\unicode`//{cooked:undefined,raw:"\\unicode"}
正则表达式反向(lookbehind)断言

首先我们得先知道什么是断言(Assertion)。

断言(Assertion)是一个对当前匹配位置之前或之后的字符的测试,它不会实际消耗任何字符,所以断言也被称为“非消耗性匹配”或“非获取匹配”。

正则表达式的断言一共有4种形式:

(?=pattern)零宽正向肯定断言(zero-widthpositivelookaheadassertion)

(?!pattern)零宽正向否定断言(zero-widthnegativelookaheadassertion)

(?=pattern)零宽反向肯定断言(zero-widthpositivelookbehindassertion)

(?!pattern)零宽反向否定断言(zero-widthnegativelookbehindassertion)

在ES9之前,JavaScript正则表达式,只支持正向断言。正向断言的意思是:当前位置后面的字符串应该满足断言,但是并不捕获。例子如下:

'fishHeadfishTail'.match(/fish(?=Head)/g)//["fish"]

反向断言和正向断言的行为一样,只是方向相反。例子如下:

'abc123'.match(/(?=(\d+)(\d+))$/)//["","1","23",index:6,input:"abc123",groups:undefined]
正则表达式Unicode转义

正则表达式中的Unicode转义符允许根据Unicode字符属性匹配Unicode字符。它允许区分字符类型,例如大写和小写字母,数学符号和标点符号。

部分例子代码如下:

//匹配所有数字constregex=/^\p{Number}+$/u;('²³¹¼½¾')//('㉛㉜㉝')//('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ')//true//匹配所有空格\p{White_Space}//匹配各种文字的所有字母,等同于Unicode版的\w[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]//匹配各种文字的所有非字母的字符,等同于Unicode版的\W[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]//匹配Emoji/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu//匹配所有的箭头字符constregexArrows=/^\p{Block=Arrows}+$/u;('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩')//true

具体的属性列表可查看:

正则表达式s/dotAll模式

在以往的版本里,JS的正则的.只能匹配emoji跟行终结符以外的所有文本,例如:

letregex=/./;('\n');//('\r');//('\u{2028}');//('\u{2029}');//('\v');//('\f');//('\u{0085}');//true//.test('foo\nbar');//false/foo[^]bar/.test('foo\nbar');//true//.test('foo\nbar');//false/foo[\s]bar/.test('foo\nbar');//true

但是在ES9之后,JS正则增加了一个新的标志s用来表示dotAll,这可以匹配任意字符。代码如下:

//('foo\nbar');//trueconstre=//s;//等价于constre=newRegExp('','s');('foo\nbar');//;//;//"s"
正则表达式命名捕获组

在以往的版本里,JS的正则分组是无法命名的,所以容易混淆。例如下面获取年月日的例子,很容易让人搞不清哪个是月份,哪个是年份:

constmatched=/(\d{4})-(\d{2})-(\d{2})/.exec('2019-01-01')(matched[0]);//2019-01-01(matched[1]);//2019(matched[2]);//01(matched[3]);//01

ES9引入了命名捕获组,允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。代码如下:

constRE_DATE=/(?year\d{4})-(?month\d{2})-(?day\d{2})/;constmatchObj=RE_('1999-12-31');constyear=;//1999constmonth=;//12constday=;//31constRE_OPT_A=/^(?asa+)?$/;constmatchObj=RE_OPT_('');//undefined'as'//true
对象扩展操作符

ES6中添加了数组的扩展操作符,让我们在操作数组时更加简便,美中不足的是并不支持对象扩展操作符,但是在ES9开始,这一功能也得到了支持,例如:

varobj1={foo:'bar',x:42};varobj2={foo:'baz',y:13};varclonedObj={obj1};//克隆后的对象:{foo:"bar",x:42}varmergedObj={obj1,obj2};//合并后的对象:{foo:"baz",x:42,y:13}

上面便是一个简便的浅拷贝。这里有一点小提示,就是()函数会触发setters,而展开语法则不会。所以不能替换也不能模拟()。

如果存在相同的属性名,只有最后一个会生效。

()

finally()方法会返回一个Promise,当promise的状态变更,不管是变成rejected或者fulfilled,最终都会执行finally()的回调。

例子如下:

fetch(url).then((res)={(res)}).catch((error)={(error)}).finally(()={('结束')})
ES10(ES2019)()/flatMap()

flat()方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

flatMap()与map()方法和深度为1的flat()几乎相同.,不过它会首先使用映射函数映射每个元素,然后将结果压缩成一个新数组,这样效率会更高。

例子如下:

vararr1=[1,2,3,4](x=[x*2])//[[2],[4],[6],[8]](x=[x*2])//[2,4,6,8]//深度为1(x=[[x*2]])//[[2],[4],[6],[8]]

flatMap()可以代替reduce()与concat(),例子如下:

vararr=[1,2,3,4](x=[x,x*2])//[1,2,2,4,3,6,4,8]//等价于((acc,x)=([x,x*2]),[])//[1,2,2,4,3,6,4,8]

但这是非常低效的,在每次迭代中,它创建一个必须被垃圾收集的新临时数组,并且它将元素从当前的累加器数组复制到一个新的数组中,而不是将新的元素添加到现有的数组中。

()/trimLeft()/trim()/trimRight()

在ES5中,我们可以通过trim()来去掉字符首尾的空格,但是却无法只去掉单边的,但是在ES10之后,我们可以实现这个功能。

如果我们要去掉开头的空格,可以使用trimStart()或者它的别名trimLeft(),

同样的,如果我们要去掉结尾的空格,我们可以使用trim()或者它的别名trimRight()。

例子如下:

constStr='Helloworld!'(Str)//'Helloworld!'(())//'Helloworld!'(())//'Helloworld!'(())//'Helloworld!'(())//'Helloworld!'

不过这里有一点要注意的是,trimStart()跟trim()才是标准方法,trimLeft()跟trimRight()只是别名。

在某些引擎里(例如Chrome),有以下的等式:

==="trimStart"==="trim"
()

()方法把键值对列表转换为一个对象,它是()的反函数。

例子如下:

constentries=newMap([['foo','bar'],['baz',42]])constobj=(entries)(obj)//Object{foo:"bar",baz:42}

description是一个只读属性,它会返回Symbol对象的可选描述的字符串。与()不同的是它不会包含Symbol()的字符串。例子如下:

Symbol('desc').toString();//"Symbol(desc)"Symbol('desc').description;//"desc"Symbol('').description;//""Symbol().description;//undefined//具名();//"Symbol()";//""//全局('foo').toString();//"Symbol(foo)"('foo').description;//"foo"

matchAll()方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。并且返回一个不可重启的迭代器。例子如下:

varregexp=/t(e)(st(\d?))/gvarstr='test1test2'(regexp)//['test1','test2'](regexp)//RegExpStringIterator{}[(regexp)]//[['test1','e','st1','1',index:0,input:'test1test2',length:4],['test2','e','st2','2',index:5,input:'test1test2',length:4]]
()返回注释与空格

在以往的版本中,()得到的字符串是去掉空白符号的,但是从ES10开始会保留这些空格,如果是原生函数则返回你控制台看到的效果,例子如下:

functionsum(a,b){returna+b;}(())//"functionsum(a,b){//returna+b;//}"(())//"functionabs(){[nativecode]}"
try-catch

在以往的版本中,try-catch里catch后面必须带异常参数,例如:

//ES10之前try{//tryCode}catch(err){//catchCode}

但是在ES10之后,这个参数却不是必须的,如果用不到,我们可以不用传,例如:

try{('Foobar')}catch{('Bar')}
BigInt

BigInt是一种内置对象,它提供了一种方法来表示大于253-1的整数。这原本是Javascript中可以用Number表示的最大数字。BigInt可以表示任意大的整数。

可以用在一个整数字面量后面加n的方式定义一个BigInt,如:10n,或者调用函数BigInt()。

在以往的版本中,我们有以下的弊端:

//大于2的53次方的整数,无法保持精度2**53===(2**53+1)//超过2的1024次方的数值,无法表示2**1024//Infinity

但是在ES10引入BigInt之后,这个问题便得到了解决。

以下操作符可以和BigInt一起使用:+、*、-、**、%。除(无符号右移)之外的位操作也可以支持。因为BigInt都是有符号的,(无符号右移)不能用于BigInt。BigInt不支持单目(+)运算符。

/操作符对于整数的运算也没问题。可是因为这些变量是BigInt而不是BigDecimal,该操作符结果会向零取整,也就是说不会返回小数部分。

BigInt和Number不是严格相等的,但是宽松相等的。

所以在BigInt出来以后,JS的原始类型便增加到了7个,如下:

Boolean

Null

Undefined

Number

String

Symbol(ES6)

BigInt(ES10)

globalThis

globalThis属性包含类似于全局对象this值。所以在全局环境下,我们有:

globalThis===this//true
import()

静态的import语句用于导入由另一个模块导出的绑定。无论是否声明了严格模式,导入的模块都运行在严格模式下。在浏览器中,import语句只能在声明了type="module"的script的标签中使用。

但是在ES10之后,我们有动态import(),它不需要依赖type="module"的script标签。

所以我们有以下例子:

constmain=("main")for(("nava")){("click",e={()import('/modules/').then(module={(main);}).catch(err={=;})})}
私有元素与方法

在ES10之前,如果我们要实现一个简单的计数器组件,我们可能会这么写:

//webcomponent写法classCounterextsHTMLElement{getx(){}setx(value){=((this))}clicked(){++}constructor(){super()=(this)=0}connectedCallback(){()}rer(){=()}}('num-counter',Counter)

但是在ES10之后我们可以使用私有变量进行组件封装,如下:

classCounterextsHTMLElement{x(){returnx(value){(this))}x++}constructor(){super();=()}()}}('num-counter',Counter)


参考资料

ECMAScript6入门()

1.5万字概括ES6全部特性

MDN

ES2018新特征之:非转义序列的模板字符串

正则表达式反向(lookbehind)断言

Unicodepropertyescapes

exnext提案

ES7、ES8、ES9、ES10新特性大盘点

EcmaTC39

[[ECMAScript]TC39process])

TheTC39Process