智慧印刷工坊

智慧印刷工坊

从ES6到ES10的新特性万字大总结(不得不收藏)

admin 182 136


介绍

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.复制代码

varletconst变量提升√××全局变量√××重复声明√××重新赋值√√×暂时死区×√√块作用域×√√只声明不初始化√√×

类(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"复制代码

xValue=0getxValue}setxValue=((){(this)}connectedCallback(){(){=this.#()}}('num-counter',Counter)


github地址: