• type: FrontEnd
  • title: ES5 to ESNext — here’s every feature added to JavaScript since 2015
  • link: medium.freecodecamp.org/es5-to-esne…
  • author: Flavio Copes
    • *

ES5 to ESNext —  自 2015 以来 JavaScript 新增的所有新个性

这篇文章的出发点是为了帮忙前端开发者串联 ES6前后的 JavaScript 常识,并且能够疾速理解 JavaScript 语言的最新进展。

JavaScript 在当下处于特权位置,因为它是惟一能够在浏览器中运行的语言,并且是被高度集成和优化过的。

JavaScript 在将来有着极好的倒退空间,跟上它的变动不会比当初更加的艰难。我的指标是让你可能疾速且全面的理解这门语言能够应用的新内容。

点击这里获取 PDF/ePub/Mobi 版本

目录

ECMAScript 简介

ES2015

  • let 和 const
  • 箭头函数
  • 默认参数
  • 模板字符串
  • 解构赋值
  • 加强的对象字面量
  • For-of 循环
  • Promises
  • 模块
  • String 新办法
  • Object 新办法
  • 开展运算符
  • Set
  • Map
  • Generators

ES2016

  • Array.prototype.includes()
  • 求幂运算符

ES2017

  • 字符串填充
  • Object.values()
  • Object.entries()
  • Object.getOwnPropertyDescriptors()
  • 尾逗号
  • 共享内存 and 原子操作

ES2018

  • Rest/Spread Properties
  • Asynchronous iteration
  • Promise.prototype.finally()
  • 正则表达式改良

ESNext

  • Array.prototype.{flat,flatMap}
  • try/catch 可选的参数绑定
  • Object.fromEntries()
  • String.prototype.{trimStart,trimEnd}
  • Symbol.prototype.description
  • JSON improvements
  • Well-formed JSON.stringify()
  • Function.prototype.toString()

ECMAScript 简介

每当浏览 JavaScript 相干的文章时,我都会常常遇到如下术语: ES3, ES5, ES6, ES7, ES8, ES2015, ES2016, ES2017, ECMAScript 2017, ECMAScript 2016, ECMAScript 2015 等等,那么它们是指代的是什么?

它们都是指代一个名为 ECMAScript 的规范。

JavaScript 就是基于这个规范实现的,ECMAScript 常常缩写为 ES。

除了 JavaScript 以外,其它基于 ECMAScript 实现语言包含:

  • ActionScript ( Flash 脚本语言),因为 Adobe 将于 2020 年末进行对 Flash 的反对而逐步失去热度。
  • JScript (微软开发的脚本语言),在第一次浏览器大战最强烈的期间,JavaScript 只被Netscape所反对,微软必须为 Internet Explorer 构建本人的脚本语言。

然而当初流传最广、影响最大的基于 ES 规范的语言实现无疑就是 JavaScript了

为啥要用这个奇怪的名字呢?Ecma International 是瑞士规范协会,负责制订国际标准。

JavaScript 被创立当前,经由 Netscape 和 Sun Microsystems 公司提交给欧洲计算机制造商协会进行标准化,被驳回的 ECMA-262 别名叫 ECMAScript

This press release by Netscape and Sun Microsystems (the maker of Java) might help figure out the name choice, which might include legal and branding issues by Microsoft which was in the committee, according to Wikipedia.

IE9 之后微软的浏览器中就看不到对 JScript 这个命名的援用了,取而代之都统称为 JavaScript。

因而,截至201x,JavaScript 成为最风行的基于 ECMAScript 标准实现的语言。

ECMAScript 以后的版本。

目前的最新的 ECMAScript 版本是 ES2018

于 2018 年 6 月公布。

TC39 是什么?

TC39(Technical Committee 39)是一个推动 JavaScript 倒退的委员会。

TC39的成员包含各个支流浏览器厂商以及业务与浏览器严密相连的公司,其中包含 Mozilla,Google ,Facebook,Apple,Microsoft,Intel,PayPal,SalesForce等。

每个规范版本提案都必须通过四个不同的阶段,这里有具体的解释。

ES Versions

令我费解的是 ES 版本的命名根据有时依据迭代的版本号,有时却依据年份来进行命名。而这个命名的不确定性又使得人们更加容易混同 JS/ES 这个两个概念?。

在 ES2015 之前,ECMAScript 各个版本的命名标准通常与跟着规范的版本更新保持一致。因而,2009年 ECMAScript 标准更新当前的的正式版本是 ES5。

Why does this happen? During the process that led to ES2015, the name was changed from ES6 to ES2015, but since this was done late, people still referenced it as ES6, and the community has not left the edition naming behind — _the world is still calling ES releases by edition number_. 为什么会产生这所有?在ES2015诞生的过程中,名称由ES6更改为ES2015,但因为最终实现太晚,人们依然称其为ES6,社区也没有将版本号齐全抛之于后 — 世界依然应用 ES 来定义版本号。

下图比拟清晰的展现了版本号与年份的关联:

接下来,咱们来深刻理解 JavaScript 自 ES5 以来减少的个性。

let和const

ES2015 之前, var 是惟一能够用来申明变量的语句。

var a = 0复制代码

下面语句如果你脱漏了 var,那么你会把这个值(0)赋给一个未声明的变量,其中申明和未声明变量之间存在一些差别。

在古代浏览器开启严格模式时,给未声明的变量赋值会抛出 ReferenceError 异样,在较老的浏览器(或者禁用严格模式)的状况下,未声明的变量在执行赋值操作时会隐式的变为全局对象的属性。

当你申明一个变量却没有进行初始化,那么它的值直到你对它进行赋值操作之前都是 undefined

var a //typeof a === 'undefined'复制代码

你能够对一个变量进行屡次从新申明,并笼罩它:

var a = 1var a = 2复制代码

你也能够在一条申明语句中一次申明多个变量:

var a = 1, b = 2复制代码

作用域是变量可拜访的代码局部。

在函数之外用 var 申明的会调配给全局对象,这种变量能够在全局作用域中被拜访到。而在函数外部申明的变量只能在函数部分作用域被拜访到,这相似于函数参数。

在函数中定义的局部变量名如何跟全局变量重名,那么局部变量的优先级更高,在函数内无法访问到同名的全局变量。

须要留神的是,var 是没有块级作用域(标识符是一对花括号)的,然而 var 是有函数作用域的,所以在新创建的块级作用域或者是函数作用域外面申明变量会笼罩全局同名变量,因为 var在这两种状况下没有创立新的作用域。

在函数外部,其中定义的任何变量在所有函数代码中都是可见的,因为JavaScript在执行代码之前实际上将所有变量都移到了顶层(被称为悬挂的货色)。 在函数的外部定义的变量在整个函数作用域中都是可见(可拜访),即便变量是在函数体开端被申明,然而依然能够再函数体结尾局部被援用,因为 JavaScript存在变量晋升机制。为防止混同,请在函数结尾申明变量,养成良好的编码标准。

Using let

let 是ES2015中引入的新性能,它实质上是具备块级作用域的 var 。它能够被以后作用域(函数以及块级作用域)以及子级作用域拜访到。

古代 JavaScript 开发者在 letvar 的抉择中可能会更偏向于前者。

如果 let 看起来是一个很形象的术语,当你浏览到 let color = 'red' 这一段,因为应用 let 定义了color 为红色,那么这所有就变的有意义了。

在任何函数之外用 let 申明变量,和 var相同的是 它并不会创立全局变量。

Using const

应用变量 varlet 申明的变量能够被从新赋值。 应用 const 申明的变量一经初始化,它的值就永远不能再扭转,即不可从新被赋值。

const a = 'test'复制代码

咱们不能再为 a 进行赋值操作。然而,a 如果它是一个具备属性或者办法的对象,那么咱们能够扭转它的属性或者办法。

const 并不意味着具备不可变性,只是保障用 const 申明的变量的援用地址不被变更。

相似于 letconst 也具备块级作用域。

古代 JavaScript 开发者在遇到不会进行二次赋值的变量申明时,应该尽量应用 const

箭头函数

箭头函数的引入极大的扭转了代码的书写格调和一些工作机制。

在我看来,箭头函数很受开发者欢送,当初很少在比拟新的代码库中看到 function 关键字了,尽管它并未被废除。

箭头函数看起来会更加的简洁,因为它容许你应用更短的语法来书写函数:

const myFunction = function() {  //...}复制代码

const myFunction = () => {  //...}复制代码

如果函数体中只蕴含一条语句,你甚至能够省略大括号并间接书写这条语句:

const myFunction = () => doSomething()复制代码

参数在括号中传递:

const myFunction = (param1, param2) => doSomething(param1, param2)复制代码

如果该函数只有一个参数,那么能够省略掉括号:

const myFunction = param => doSomething(param)复制代码

因为这种简短的语法,使得咱们能够更便捷的应用比拟简短的函数

隐式返回

箭头函数反对隐式返回:能够失常的 return 一个返回值然而能够不应用 return 关键字。

隐式返回只在函数体内只蕴含一条语句的状况下失效:

const myFunction = () => 'test'myFunction() //'test'复制代码

须要留神的一种状况,当返回一个对象时,记得将大括号括在括号中以防止产生歧义,误将其(大括号)解析为函数体的大括号。

const myFunction = () => ({ value: 'test' })myFunction() //{value: 'test'}复制代码

箭头函数中的 this

this 可能是一个很难把握的概念,因为它会依据上下文而进行变动,并且会在不同的 JavaScript的模式(是否为_严格模式_)下体现出差别。

了解 this 这个概念对于箭头函数的应用很重要,因为与惯例函数相比,箭头函数的体现十分不同。

对象的办法为惯例函数时,办法中的this指向这个对象,因而能够这样做:

const car = {  model: 'Fiesta',  manufacturer: 'Ford',  fullName: function() {    return `${this.manufacturer} ${this.model}`  }}复制代码

执行 car.fullName() 会返回 "Ford Fiesta"

如果上述办法应用是是箭头函数,因为箭头中的 this 的作用域继承自执行上下文,箭头函数本身不绑定 this,因而 this 的值将在调用堆栈中查找,因而在此代码 car.fullName() 中不会返回惯例函数那样的后果,理论会返回字符串 "undefined undefined":

const car = {  model: 'Fiesta',  manufacturer: 'Ford',  fullName: () => {    return `${this.manufacturer} ${this.model}`  }}复制代码

因而,箭头函数不适宜作为对象办法。

同样,箭头函数也不适宜应用在作为创立构造函数,因为在实例化对象时会抛出 TypeError

所以在不须要动静上下文时请应用惯例函数。

当然,在事件监听器上应用箭头函数也会存在问题。因为 DOM 事件侦听器会主动将 this 与指标元素绑定,如果该事件处理程序的逻辑依赖 this,那么须要惯例函数:

const link = document.querySelector('#link')link.addEventListener('click', () => {  // this === window})const link = document.querySelector('#link')link.addEventListener('click', function() {  // this === link})复制代码

Classes类

JavaScript 实现继承的形式比拟常见:原型继承。原型继承尽管在我看来很棒,但与其它大多数风行的编程语言的继承实现机制不同,后者是基于类的。

因而 Java、Python 或其它语言的开发者很难了解原型继承的形式,因而 ECMAScript 委员会决定在原型继承之上实现 class 的语法糖,这样便于让其它基于类实现继承的语言的开发者更好的了解 JavaScript 代码。

留神:class 并没有对 JavaScript 底层做批改,你依然能够间接拜访对象原型。

class 定义

如下是一个 class 的例子:

class Person {  constructor(name) {    this.name = name  }  hello() {    return 'Hello, I am ' + this.name + '.'  }}复制代码

class 具备一个标识符,咱们能够应用 new ClassIdentifier() 来创立一个对象实例。

初始化对象时,调用 constructor办法,并将参数传递给此办法。

类申明语句中也能够减少类须要的一些原型办法。在这种状况下 helloPerson 类的一个原型办法,能够在这个类的对象实例上调用:

const flavio = new Person('Flavio')flavio.hello()复制代码

Class 继承

一个子类能够 extend 另一个类,通过子类实例化进去的对象能够继承这两个类的所有办法。

如果子类中的办法与父类中的办法名反复,那么子类中的同名办法优先级更高:

class Programmer extends Person {  hello() {    return super.hello() + ' I am a programmer.'  }}const flavio = new Programmer('Flavio')flavio.hello()复制代码

(上述代码会打印出:“_Hello, I am Flavio. I am a programmer._”)

类没有显示的类变量申明,但你必须在初始化构造函数 constructor 中去初始化类成员变量。

在子类中,你能够通过调用super()援用父类。

静态方法

在类中,通常会把办法间接挂载到实例对象上,间接在实例对象上调用。

而静态方法则是间接应用类名来调用,而不是通过对象实例调用:

class Person {  static genericHello() {    return 'Hello'  }}Person.genericHello() //Hello复制代码

公有办法

JavaScript 没有内置真正意义上的受爱护的公有办法。

社区有解决办法,但我不会在这里做解说。

Getters 和 setters

你能够通过减少办法 前缀 get 或者 set 创立一个 getter 和 setter,getter 和 setter会在你去获取特定值或者批改特定值的时候执行 get 或者 set内的相干办法。

class Person {  constructor(name) {    this._name = name  }  set name(value) {    this._name = value  }  get name() {    return this._name  }}复制代码

如果你只有 getter,该属性无奈被设置,并且设置此属性的操作都会被疏忽:

class Person {  constructor(name) {    this._name = name  }  get name() {    return this._name  }}复制代码

如果你只有一个 setter,则能够更改该值,但不能从内部拜访它:

class Person {  constructor(name) {    this._name = name  }  set name(value) {    this._name = value  }}复制代码

默认参数

函数 doSomething 接管一个 param1 参数。

const doSomething = (param1) => {}复制代码

咱们能够给 param1 设定默认值,如果在调用函数时未传入参数,那么该参数主动设定未默认值。

const doSomething = (param1 = 'test') => {}复制代码

当然,这种机制同样实用于多个参数:

const doSomething = (param1 = 'test', param2 = 'test2') => {}复制代码

如果你的函数是一个具备特定属性的对象该怎么解决?

曾几何时,如果咱们必须要取一个对象的特定属性值,为了做兼容解决(对象格局不正确),你必须在函数中增加一些代码:

const colorize = (options) => {  if (!options) {    options = {}  }  const color = ('color' in options) ? options.color : 'yellow'  ...}复制代码

通过解构,你能够给特定属性提供默认值,如此能够大大简化代码:

const colorize = ({ color = 'yellow' }) => {  ...}复制代码

如果在调用 colorize 函数时没有传递任何对象,咱们同样能够失去一个默认对象作为参数以供应用:

const spin = ({ color = 'yellow' } = {}) => {  ...}复制代码

模板字符串

模板字符串不同于 ES5 以前的版本,你能够用新鲜的形式应用字符串。

这个语法看起来十分简便,只须要应用一个反引号替换掉单引号或双引号:

const a_string = `something`复制代码

这个用法是举世无双的,因为它提供了许多一般字符串所没有的性能,如下:

  • 它为定义多行字符串提供了一个很好的语法
  • 它提供了一种在字符串中插入变量和表达式的简略办法
  • 它容许您创立带有模板标签的DSL (DSL意味着畛域特定语言,例如:就如同在 React 中应用 styled-components 定义你组件的 CSS 一样)

上面让咱们深刻每个性能的细节。

多行字符串

在 ES6 规范之前,创立逾越两行的字符串只能在一行的结尾应用 '' 字符:

const string =  'first part second part'复制代码

这样使得你创立的字符串尽管逾越了两汉,然而渲染时依然体现成一行:

first part second part复制代码

须要渲染为多行的话,须要在一行结尾增加 'n',比方这样:

const string =  'first linen second line'复制代码

或者

const string = 'first linen' + 'second line'复制代码

模板字符串使得定义多行字符串变得更加简便。

一个模板字符串由一个反引号开始,你只须要按下回车键来创立新的一行,不须要插入特殊符号,最终的渲染成果如下所示:

const string = `Heythisstringis awesome!`复制代码

须要特地注意空格在这里是有非凡意义的,如果这样做的话:

const string = `First                Second`复制代码

那么它会创立出像上面的字符串:

First                Second复制代码

有一个简略的办法能够修复这个问题,只须要将第一行置为空,而后增加了左边的翻译好后调用一个 trim() 办法,就能够打消第一个字符前的所有空格:

const string = `FirstSecond`.trim()复制代码

插值

模板字符串提供了插入变量和表达式的便捷办法

你只须要应用 ${...} 语法

const var = 'test'const string = `something ${var}` //something test复制代码

在 ${} 外面你能够退出任何货色,甚至是表达式:

const string = `something ${1 + 2 + 3}`const string2 = `something ${foo() ? 'x' : 'y'}`复制代码

Template tags

标记模板可能是一个听起来不太有用的性能,但它实际上被许多风行的库应用,如 Styled Components 、Apollo 、GraphQL客户端/服务器库,因而理解它的工作原理至关重要。

在 Styled Components 模板标签中用于定义CSS字符串

const Button = styled.button`  font-size: 1.5em;  background-color: black;  color: white;`复制代码

在 Apollo 中,模板标签用于定义 GraphQL 查问模式:

const query = gql`  query {    ...  }`复制代码

下面两个例子中的styled.buttongql模板标签其实都是函数:

function gql(literals, ...expressions) {}复制代码

这个函数返回一个字符串,能够是_任意_类型的计算结果。

字面量(literals)是一个蕴含了表达式插值的模板字面量的序列。 表达式(expressions)蕴含了所有的插值。

举个例子:

const string = `something ${1 + 2 + 3}`复制代码

这个例子外面的字面量是由2个局部组成的序列。第1局部就是something,也就是第一个插值地位(${})之前的字符串,第2局部就是一个空字符串,从第1个插值完结的地位直到字符串的完结。

这个例子外面的表达式就是只蕴含1个局部的序列,也就是6

举一个更简单的例子:

const string = `somethinganother ${'x'}new line ${1 + 2 + 3}test`复制代码

这个例子外面的字面量的序列外面,第1个局部是:

;`somethinganother `复制代码

第2局部是:

;`new line `复制代码

第3局部是:

;`test`复制代码

这个例子外面的表达式蕴含了2个局部:x6

拿到了这些值的函数就能够对其做任意解决,这就是这个个性的威力所在。

比方最简略的解决就是字符串插值,把字面量表达式拼接起来:

const interpolated = interpolate`I paid ${10}`复制代码

插值的过程就是:

function interpolate(literals, ...expressions) {  let string = ``  for (const [i, val] of expressions) {    string += literals[i] + val  }  string += literals[literals.length - 1]  return string}复制代码

解构赋值

给定一个object,你能够抽取其中的一些值并且赋值给命名的变量:

const person = {  firstName: 'Tom',  lastName: 'Cruise',  actor: true,  age: 54, //made up}const {firstName: name, age} = person复制代码

nameage就蕴含了对应的值。

这个语法同样能够用到数组当中:

const a = [1,2,3,4,5]const [first, second] = a复制代码

上面这个语句创立了3个新的变量,别离取的是数组a的第0、1、4下标对应的值:

const [first, second, , , fifth] = a复制代码

更弱小的对象字面量

ES2015赋予了对象字面量更大的威力。

简化了蕴含变量的语法

原来的写法:

const something = 'y'const x = {  something: something}复制代码

新的写法:

const something = 'y'const x = {  something}复制代码

原型

原型能够这样指定:

const anObject = { y: 'y' }const x = {  __proto__: anObject}复制代码

super()

const anObject = { y: 'y', test: () => 'zoo' }const x = {  __proto__: anObject,  test() {    return super.test() + 'x'  }}x.test() //zoox复制代码

动静属性

const x = {  ['a' + '_' + 'b']: 'z'}x.a_b //z复制代码

For-of循环

2009年的ES5引入了forEach()循环,尽管很好用,然而它跟for循环不一样,没法break。

ES2015引入了**for-of** 循环,就是在forEach的根底上加上了break的性能:

//iterate over the valuefor (const v of ['a', 'b', 'c']) {  console.log(v);}//get the index as well, using `entries()`for (const [i, v] of ['a', 'b', 'c'].entries()) {  console.log(index) //index  console.log(value) //value}复制代码

注意一下const的应用。这个循环在每次迭代中都会创立一个新的作用域,所以咱们能够应用const来代替let

它跟for...in的区别在于:

  • for...of 遍历属性值
  • for...in 遍历属性名

Promises

promise的个别定义: 它是一个代理,通过它能够最终失去一个值.

Promise是解决异步代码的一种形式,能够少写很多回调。

异步函数是建设在promise API下面的,所以了解Promise是一个根本的要求。

promise的原理简述

一个promise被调用的时候,首先它是处于pending状态。在promise解决的过程中,调用的函数(caller)能够继续执行,直到promise给出反馈。

此时,调用的函数期待的promise后果要么是resolved状态,要么是rejected状态。然而因为JavaScript是异步的,所以_promise解决的过程中,函数会继续执行_。

为什么JS API应用promises?

除了你的代码和第三方库的代码之外,promise在用在古代的Web API中,比方:

  • 电池API
  • Fetch API
  • Service Workers

在古代的JavaScript中,不应用promise是不太可能的,所以咱们来深入研究下promise吧。

创立一个promise

Promise API裸露了一个Promise构造函数,能够通过new Promise()来初始化:

let done = trueconst isItDoneYet = new Promise((resolve, reject) => {  if (done) {    const workDone = 'Here is the thing I built'    resolve(workDone)  } else {    const why = 'Still working on something else'    reject(why)  }})复制代码

promise会查看done这个全局变量,如果为true,就返回一个resolved promise,否则就返回一个rejected promise。

通过resolvereject,咱们能够失去一个返回值,返回值能够是字符串也能够是对象。

应用一个promise

下面讲了怎么创立一个promise,上面就讲怎么应用(consume)这个promise。

const isItDoneYet = new Promise()//...const checkIfItsDone = () => {  isItDoneYet    .then(ok => {      console.log(ok)    })    .catch(err => {      console.error(err)    })}复制代码

运行checkIfItsDone()办法时,会执行isItDoneYet()这个promise,并且期待它resolve的时候应用then回调,如果有谬误,就用catch回调来解决。

链式promise

一个promise能够返回另一个promise,从而创立promise链条(chain)。

一个很好的例子就是Fetch API,它是基于XMLHttpRequest API的一个下层API,咱们能够用它来获取资源,并且在获取到资源的时候链式执行一系列promise。

Fetch API是一个基于promise的机制,调用fetch()相当于应用new Promise()来申明咱们本人的promise。

链式promise的例子

const status = response => {  if (response.status >= 200 && response.status < 300) {    return Promise.resolve(response)  }  return Promise.reject(new Error(response.statusText))}const json = response => response.json()fetch('/todos.json')  .then(status)  .then(json)  .then(data => {    console.log('Request succeeded with JSON response', data)  })  .catch(error => {    console.log('Request failed', error)  })复制代码

在这个例子当中,咱们调用fetch(),从根目录的todos.json文件中获取一系列的TODO我的项目,并且创立一个链式promise。

运行fetch()办法会返回一个response,它蕴含很多属性,咱们从中援用如下属性:

  • status, 一个数值,示意HTTP状态码
  • statusText, 一个状态音讯,当申请胜利的时候返回OK

response还有一个json()办法,它返回一个promise,返回内容转换成JSON后的后果。

所以这些promise的调用过程就是:第一个promise执行一个咱们定义的status()办法,查看response status,判断是否一个胜利的响应(status在200和299之间),如果不是胜利的响应,就reject这个promise。

这个reject操作会导致整个链式promise跳过前面的所有promise间接到catch()语句,打印Request failed和谬误音讯。

如果这个promise胜利了,它会调用咱们定义的json()函数。因为后面的promise胜利之后返回的response对象,咱们能够拿到并作为第2个promise的参数传入。

在这个例子外面,咱们返回了JSON序列化的数据,所以第3个promise间接接管这个JSON:

.then((data) => {  console.log('Request succeeded with JSON response', data)})复制代码

而后咱们把它打印到console。

处理错误

在上一节的的例子外面,咱们有一个catch接在链式promise前面。

当promise链中的任意一个出错或者reject的时候,就会间接跳到promise链前面最近的catch()语句。

new Promise((resolve, reject) => {  throw new Error('Error')}).catch(err => {  console.error(err)})// ornew Promise((resolve, reject) => {  reject('Error')}).catch(err => {  console.error(err)})复制代码

级联谬误

如果在catch()外面抛出一个谬误,你能够在前面接上第二个catch()来解决这个谬误,以此类推。

new Promise((resolve, reject) => {  throw new Error('Error')})  .catch(err => {    throw new Error('Error')  })  .catch(err => {    console.error(err)  })复制代码

组织多个promise

Promise.all()

如果你要同时实现不同的promise,能够用Promise.all()来申明一系列的promise,而后当它们全副resolve的时候再执行一些操作。

例子:

const f1 = fetch('/something.json')const f2 = fetch('/something2.json')Promise.all([f1, f2])  .then(res => {    console.log('Array of results', res)  })  .catch(err => {    console.error(err)  })复制代码

联合ES2015的解构赋值语法,你能够这样写:

Promise.all([f1, f2]).then(([res1, res2]) => {  console.log('Results', res1, res2)})复制代码

当然这不限于应用fetch这实用于任何promise.

Promise.race()

Promise.race()运行所有传递进去的promise,然而只有有其中一个resolve了,就会运行回调函数,并且只执行一次回调,回调的参数就是第一个resolve的promise返回的后果。

例子:

const promiseOne = new Promise((resolve, reject) => {  setTimeout(resolve, 500, 'one')})const promiseTwo = new Promise((resolve, reject) => {  setTimeout(resolve, 100, 'two')})Promise.race([promiseOne, promiseTwo]).then(result => {  console.log(result) // 'two'})复制代码

模块

ES Module是用于解决模块的ECMAScript规范。

尽管 Node.js 多年来始终应用 CommonJS规范,但浏览器却从未有过模块零碎,因为模块零碎的决策首先须要 ECMAScript 标准化后才由浏览器厂商去施行实现。

这个标准化曾经实现在 ES2015中,浏览器也开始施行实现这个规范,大家试图保持一致,以雷同的形式工作。当初 ES Module 能够在 Chrome Safari Edge 和 Firefox(从60版本开始) 中应用。

模块十分酷,他们能够让你封装各种各样的性能,同时将这些性能作为库裸露给其它 JavaScript 文件应用。

ES 模块语法

引入模块的语法:

import package from 'module-name'复制代码

CommonJS 则是这样应用:

const package = require('module-name')复制代码

一个模块是一个 JavaScript 文件,这个文件应用 export 关键字 导出 一个或多个值(对象、函数或者变量)。例如,上面这个模块提供了一个将字符串变成大写模式的函数:

uppercase.js
export default str => str.toUpperCase()复制代码

在这个例子中,这个模块定义了惟一一个 default export,因而能够是一个匿名函数。否则,须要一个名称来和其它 导出 做辨别。

当初,任何其它的 JavaScript 模块 能够通过 import 导入 uppercase.js 的这个性能。

一个 HTML 页面能够通过应用了非凡的 type=module 属性的 <script> 标签增加一个模块。

<script type="module" src="index.js"></script>复制代码
留神: 这个模块导入的行为就像 *defer* 脚本加载一样。具体能够看 efficiently load JavaScript with defer and async

须要特地留神的是,任何通过 type="module" 载入的脚本会应用 严格模式 加载。

在这个例子中,uppercase.js 模块定义了一个 default export,因而当咱们在导入它的时候,咱们能够给他起一个任何咱们喜爱的名字:

import toUpperCase from './uppercase.js'复制代码

同时咱们能够这样应用它:

toUpperCase('test') //'TEST'复制代码

你也能够通过一个绝对路径来导入模块,上面是一个援用来自其它域底下定义的模块的例子:

import toUpperCase from 'https://flavio-es-modules-example.glitch.me/uppercase.js'复制代码

上面同样是一些非法的 _import_语法:

import { toUpperCase } from '/uppercase.js'import { toUpperCase } from '../uppercase.js'复制代码

上面是谬误的应用:

import { toUpperCase } from 'uppercase.js'import { toUpperCase } from 'utils/uppercase.js'复制代码

因为这里既不是应用相对地址,也不是应用的绝对地址。

其它的 import/export 语法

咱们理解了下面的例子:

export default str => str.toUpperCase()复制代码

这里生成了一个 default export_。然而,你能够通过上面的语法在一个文件外面 _导出 多个性能:

const a = 1const b = 2const c = 3export { a, b, c }复制代码

另外一个模块能够应用上面的形式 import 导入所有:

import * from 'module'复制代码

你也能够通过解构赋值的形式仅仅 import 导出一部分:

import { a } from 'module'import { a, b } from 'module'复制代码

为了不便,你还能够应用 as 重命名任何 import 的货色:

import { a, b as two } from 'module'复制代码

你能够导入模块中的默认进口以及通过名称导入任何非默认的进口:

import React, { Component } from 'react'复制代码

这是一篇对于 ES 模块的文章,能够看一下: glitch.com/edit/#!/fla…

CORS(跨域资源共享)

进行近程获取模块的时候是遵循 CORS 机制的。这意味着当你援用近程模块的时候,必须应用非法的 CORS 申请头来容许跨域拜访(例如:Access-Control-Allow-Origin: *)。

对于不反对模块的浏览器应该怎么做?

联合 type="module"nomodule 一起应用:

<script type="module" src="module.js"></script><script nomodule src="fallback.js"></script>复制代码

包装模块

ES 模块是古代浏览器中的一大个性。这些个性是 ES6 标准中的一部分,要在浏览器中全副实现这些个性的路还很漫长。

咱们当初就能应用它们!然而咱们同样须要晓得,有一些模块会对咱们的页面性能产生性能影响。因为浏览器必须要在运行时执行它们。

Webpack 可能依然会被大量应用,即便 ES 模块能够在浏览器中执行。然而语言内置这个个性对于客户端和 nodejs 在应用模块的时候是一种微小的对立。

新的字符串办法

任何字符串有了一些实例办法:

  • repeat()
  • codePointAt()

repeat()

依据指定的次数反复字符串:

'Ho'.repeat(3) //'HoHoHo'复制代码

没有提供参数以及应用 0 作为参数的时候返回空字符串。如果给一个正数参数则会失去一个 RangeError 的谬误。

codePointAt()

这个办法能用在解决那些须要 2 个 UTF-16 单元示意的字符上。

应用 charCodeAt 的话,你须要先别离失去两个 UTF-16 的编码而后联合它们。然而应用 codePointAt() 你能够间接失去整个字符。

上面是一个例子,中文的 “?” 是由两个 UTF-16 编码组合而成的:

"?".charCodeAt(0).toString(16) //d842"?".charCodeAt(1).toString(16) //dfb7复制代码

如果你将两个 unicode 字符组合起来:

"ud842udfb7" //"?"复制代码

你也能够用 codePointAt() 失去同样的后果:

"?".codePointAt(0) //20bb7复制代码

如果你将失去的 unicode 编码组合起来:

"u{20bb7}" //"?"复制代码

更多对于 Unicode 的应用办法,参考我的Unicode guide。

新的对象办法

ES2015 在 Object 类下引入了一些静态方法:

  • Object.is() 确定两个值是不是同一个
  • Object.assign() 用来浅拷贝一个对象
  • Object.setPrototypeOf 设置一个对象的原型

Object.is()

这个办法用来帮忙比拟对象的值:

应用形式:

Object.is(a, b)复制代码

返回值在下列状况之外始终是 false

  • ab 是同一个对象
  • ab 是相等的字符串(用同样的字符组合在一起的字符串是相等的)
  • ab 是相等的数字
  • ab 都是 undefined, null, NaN, true 或者都是 false

0-0 在 JavaScript 外面是不同的值, 所以对这种状况要多加小心(例如在比拟之前,应用 +一元操作符将所有值转换成 +0)。

Object.assign()

ES2015 版本中引入,这个办法拷贝所有给出的对象中的可枚举的本身属性到另一个对象中。

这个 API 的根本用法是创立一个对象的浅拷贝。

const copied = Object.assign({}, original)复制代码

作为浅拷贝,值会被复制,对象则是拷贝其援用(不是对象自身),因而当你批改了源对象的一个属性值,这个批改也会在拷贝出的对象中失效,因为外部援用的对象是雷同的。:

const original = {  name: 'Fiesta',  car: {    color: 'blue'  }}const copied = Object.assign({}, original)original.name = 'Focus'original.car.color = 'yellow'copied.name //Fiestacopied.car.color //yellow复制代码

我之前提到过,源对象能够是一个或者多个:

const wisePerson = {  isWise: true}const foolishPerson = {  isFoolish: true}const wiseAndFoolishPerson = Object.assign({}, wisePerson, foolishPerson)console.log(wiseAndFoolishPerson) //{ isWise: true, isFoolish: true }复制代码

Object.setPrototypeOf()

设置一个对象的原型。能够承受两个参数:对象以及原型。

应用办法:

Object.setPrototypeOf(object, prototype)复制代码

例子:

const animal = {  isAnimal: true}const mammal = {  isMammal: true}mammal.__proto__ = animalmammal.isAnimal //trueconst dog = Object.create(animal)dog.isAnimal  //trueconsole.log(dog.isMammal)  //undefinedObject.setPrototypeOf(dog, mammal)dog.isAnimal //truedog.isMammal //true复制代码

开展操作符

你能够开展一个数组、一个对象甚至是一个字符串,通过应用开展操作符 ...

让咱们以数组来举例,给出:

const a = [1, 2, 3]复制代码

你能够应用上面的形式创立出一个新的数组:

const b = [...a, 4, 5, 6]复制代码

你也能够像上面这样创立一个数组的拷贝:

const c = [...a]复制代码

这中形式对于对象依然无效。应用上面的形式克隆一个对象:

const newObj = { ...oldObj }复制代码

用在字符串上的时候,开展操作符会以字符串中的每一个字符创立一个数组:

const hey = 'hey'const arrayized = [...hey] // ['h', 'e', 'y']复制代码

这个操作符有一些十分有用的利用。其中最重要的一点就是以一种非常简单的形式应用数组作为函数参数的能力:

const f = (foo, bar) => {}const a = [1, 2]f(...a)复制代码

(在之前的语法标准中,你只能通过 f.apply(null, a) 的形式来实现,然而这种形式不是很敌对和易读。)

残余参数(rest element)在和数组解构(array destructuring)搭配应用的时候十分有用。

const numbers = [1, 2, 3, 4, 5][first, second, ...others] = numbers复制代码

上面是开展元素 (spread elements):

const numbers = [1, 2, 3, 4, 5]const sum = (a, b, c, d, e) => a + b + c + d + econst sum = sum(...numbers)复制代码

ES2018 引入了 残余属性 ,同样的操作符然而只能用在对象上。

残余属性(Rest properties):

const { first, second, ...others } = {  first: 1,  second: 2,  third: 3,  fourth: 4,  fifth: 5}first // 1second // 2others // { third: 3, fourth: 4, fifth: 5 }复制代码

属性开展(Spread properties)容许咱们联合跟在 ... 操作符之后对象的属性:

const items = { first, second, ...others }items //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }复制代码

Set

一个 Set 数据结构容许咱们在一个容器外面减少数据。

一个 Set 是一个对象或者根底数据类型(strings、numbers或者booleans)的汇合,你能够将它看作是一个 Map,其中值作为映射键,map 值始终为 true。

初始化一个 Set

Set 能够通过上面的形式初始化:

const s = new Set()复制代码

向 Set 中增加一项

你能够应用 add 办法向 Set 中增加项:

s.add('one')s.add('two')复制代码

Set 仅会存贮惟一的元素,因而屡次调用 s.add('one') 不会反复增加新的元素。

你不能够同时向 set 中退出多个元素。你须要屡次调用 add() 办法。

查看元素是否在 set 中

咱们能够通过上面的形式查看元素是否在 set 中:

s.has('one') //trues.has('three') //false复制代码

从 set 中删除一个元素:

应用 delete() 办法:

s.delete('one')复制代码

确定 set 中元素的数量

应用 size 属性:

s.size复制代码

删除 set 中的全副元素

应用 clear() 办法:

s.clear()复制代码

对 set 进行迭代

应用 keys() 或者 values() 办法 - 它们等价于上面的代码:

for (const k of s.keys()) {  console.log(k)}for (const k of s.values()) {  console.log(k)}复制代码

entries() 办法返回一个迭代器,你能够这样应用它:

const i = s.entries()console.log(i.next())复制代码

调用 i.next() 将会以 { value, done = false } 对象的模式返回每一个元素,直到迭代完结,这时 donetrue

你也能够调用 set 的 forEach() 办法:

s.forEach(v => console.log(v))复制代码

或者你就间接应用 for..of 循环吧:

for (const k of s) {  console.log(k)}复制代码

应用一些初始值初始化一个 set

你能够应用一些值初始化一个 set:

const s = new Set([1, 2, 3, 4])复制代码

将 set 转换为一个数组

const a = [...s.keys()]// orconst a = [...s.values()]复制代码

WeakSet

一个 WeakSet 是一个非凡的 Set.

在 set 中,元素不会被 gc(垃圾回收)。一个 weakSet 让它的所有元素都是能够被 gc 的。weakSet 中的每个键都是一个对象。当这个对象的援用隐没的时候,对应的值就能够被 gc 了。

上面是次要的不同点:

  1. WeakSet 不可迭代
  2. 你不能清空 weakSet 中的所有元素
  3. 不可能失去 weakSet 的大小

一个 weakSet 通常是在框架级别的代码中应用,仅仅裸露了上面的办法:

  • add()
  • has()
  • delete()

Map

一份map构造的数据容许咱们建设数据和key的关系

在ES6之前

在引入Map之前,开发者通常把对象(Object)当Map应用,把某个object或value值与指定的key进行关联:

const car = {}car['color'] = 'red'car.owner = 'Flavio'console.log(car['color']) //redconsole.log(car.color) //redconsole.log(car.owner) //Flavioconsole.log(car['owner']) //Flavio复制代码

引入Map之后

ES6引入了Map数据结构,它为咱们解决这种数据结构提供了一种适合的工具

Map的初始化:

const m = new Map()复制代码

增加条目到Map中

你能够通过set()办法把条目设定到map中:

m.set('color', 'red')m.set('age', 2)复制代码

通过key值从map中获取条目

你能够通过get()办法从map中取出条目:

const color = m.get('color')const age = m.get('age')复制代码

通过key值从map中删除条目

应用delete()办法:

m.delete('color')复制代码

从map中删除所有条目

应用clear()办法:

m.clear()复制代码

通过key值查看map中是否含有某个条目

应用has()办法

const hasColor = m.has('color')复制代码

获取map中的条目数量

应用 size 属性:

const size = m.size复制代码

用value值初始化一个map

你能够用一组value来初始化一个map:

const m = new Map([['color', 'red'], ['owner', 'Flavio'], ['age', 2]])复制代码

Map 的key值

任何值(对象,数组,字符串,数字)都能够作为一个map的value值(应用key-value键值的模式),任何值也能够用作key,即便是object对象。

如果你想通过get()办法从map中获取不存在的key,它将会返回undefined

在真实世界中你简直不可能找到的诡异状况

const m = new Map()m.set(NaN, 'test')m.get(NaN) //testconst m = new Map()m.set(+0, 'test')m.get(-0) //test复制代码

应用Iterate迭代器获取map的keys值

Map提供了keys()办法,通过该办法咱们能够迭代出所有的key值:

for (const k of m.keys()) {  console.log(k)}复制代码

应用Iterate迭代器获取map的values值

Map提供了values()办法,通过该办法咱们能够迭代出所有的value值:

for (const v of m.values()) {  console.log(v)}复制代码

应用Iterate迭代器获取key-value组成的键值对

Map提供了entries()办法,通过该办法咱们能够迭代出所有的键值对:

for (const [k, v] of m.entries()) {  console.log(k, v)}复制代码

应用办法还能够简化为:

for (const [k, v] of m) {  console.log(k, v)}复制代码

将map的keys值转换为数组

const a = [...m.keys()]复制代码

将map的values值转换为数组

const a = [...m.values()]复制代码

WeakMap

WeakMap是一种非凡的Map

在一个map对象中,定义在其上数据永远不会被垃圾回收,WeakMap替而代之的是它容许在它下面定义的数据能够自在的被垃圾回收走,WeakMap的每一个key都是一个对象,当指向该对象的指针失落,与之对应的value就会被垃圾回收走。

这是WeakMap的次要不同处:

  1. 你不能够在WeakMap上迭代keys值和values值(或者key-value键值对)
  2. 你不能够从WeakMap上革除所有条目
  3. 你不能够获取WeakMap的大小

WeakMap提供了如下几种办法,这些办法的应用和在Map中一样:

  • get(k)
  • set(k, v)
  • has(k)
  • delete(k)

对于WeakMap的用例不如Map的用例那么显著,你可能永远也不会在哪里会用到它,但从理论登程,WeakMap能够构建不会烦扰到垃圾回收机制的内存敏感性缓存,还能够满足封装的严谨性及信息的暗藏性需求。

Generators生成器

Generators是一种非凡的函数,它可能暂停本身的执行并在一段时间后再持续运行,从而容许其它的代码在此期间运行(无关该主题的具体阐明,请参阅残缺的“javascript生成器指南”)。

Generators的代码决定它必须期待,因而它容许队列中的其它代码运行,并保留“当它期待的事件”实现时复原其操作的势力。

所有这一切都是通过一个简略的关键字“yield`”实现的。当生成器蕴含该关键字时,将进行执行。

generator生成器能够蕴含许多yield关键字,从而使本人能屡次进行运行,它是由*function关键字标识(不要将其与C、C++或Go等低级语言中应用的勾销指针援用操作符混同)。

Generators反对JavaScript中全新的编程范式,包含:

  • 在generator运行时反对双向通信
  • 不会“解冻”长期运行在程序中的while循环

这里有一个解释generator如何工作的例子:

function *calculator(input) {  var doubleThat = 2 * (yield (input / 2))  var another = yield (doubleThat)  return (input * doubleThat * another)}复制代码

咱们先初始化它:

const calc = calculator(10)复制代码

而后咱们在generator中开始进行iterator迭代:

calc.next()复制代码

第一个迭代器开始了迭代,代码返回如下object对象:

{  done: false  value: 5}复制代码

具体过程如下:代码运行了函数,并把input=10传入到生成器构造函数中,该函数始终运行直到到达yield,并返回yield输入的内容: input / 2 = 5,因而,咱们失去的值为5,并告知迭代器还没有done(函数只是暂停了)。

在第二个迭代处,咱们输出7:

calc.next(7)复制代码

而后咱们失去了后果:

{  done: false  value: 14}复制代码

7被作为doubleThat的值,留神:你可能会把input/2作为输出参数,但这只是第一次迭代的返回值。当初咱们疏忽它,应用新的输出值7,并将其乘以2.

而后,咱们失去第二个yield的值,它返回doubleThat,因而返回值为14

在下一个,也是最初一个迭代器,咱们输出100

calc.next(100)复制代码

这样咱们失去:

{  done: true  value: 14000}复制代码

当迭代器实现时(没有更多的yield关键字),咱们返回input * doubleThat * another,这相当于10 * 14 * 100


这些都是在2015年的ES2015引入的个性,当初咱们深刻理解下ES2016,它的作用域范畴更小。


Array.prototype.includes()

该个性引入了一种更简洁的语法,同来查看数组中是否蕴含指定元素。

对于ES6及更低版本,想要查看数组中是否蕴含指定元素,你不得不应用indexOf办法,它查看数组中的索引,如果元素不存在,它返回-1,因为-1被计算为true,你需对其进行取反操作,例子如下:

if (![1,2].indexOf(3)) {  console.log('Not found')}复制代码

通过ES7引入的新个性,咱们能够如此做:

if (![1,2].includes(3)) {  console.log('Not found')}复制代码

求幂运算符

求幂运算符**相当于Math.pow()办法,然而它不是一个函数库,而是一种语言机制:

Math.pow(4, 2) == 4 ** 2复制代码

对于须要进行密集数学运算的程序来说,这个个性是个很好的加强,在很多语言中,**运算符都是规范(包含Python、Ruby、MATLAB、Perl等其它多种语言)。


这些都是2016年引入的个性,当初让咱们进入2017年。


字符串填充

字符串填充的目标是给字符串增加字符,以使其达到指定长度

ES2017引入了两个String办法:padStart()padEnd()

padStart(targetLength [, padString])padEnd(targetLength [, padString])复制代码

应用例子:

Object.values()

该办法返回一个数组,数组蕴含了对象本人的所有属性,应用如下:

const person = { name: 'Fred', age: 87 }Object.values(person) // ['Fred', 87]复制代码

Object.values()也能够作用于数组:

const people = ['Fred', 'Tony']Object.values(people) // ['Fred', 'Tony']复制代码

Object.entries()

该办法返回一个数组,数组蕴含了对象本人的所有属性键值对,是一个[key, value]模式的数组,应用如下:

const person = { name: 'Fred', age: 87 }Object.entries(person) // [['name', 'Fred'], ['age', 87]]复制代码

Object.entries()也能够作用于数组:

const people = ['Fred', 'Tony']Object.entries(people) // [['0', 'Fred'], ['1', 'Tony']]复制代码

Object.getOwnPropertyDescriptors()

该办法返回本人(非继承)的所有属性描述符,JavaScript中的任何对象都有一组属性,每个属性都有一个描述符,描述符是属性的一组属性(attributes),由以下局部组成:

  • value: 相熟的value值
  • writable: 属性是否能够被更改
  • get: 属性的getter函数, 当属性读取时被调用
  • set: 属性的setter函数, 当属性设置值时被调用
  • configurable: 如果为false, 不能删除该属性,除了它的value值认为,也不能更改任何属性。
  • enumerable: 该属性是否能枚举

Object.getOwnPropertyDescriptors(obj)承受一个对象,并返回一个带有描述符汇合的对象。

In what way is this useful?

ES6给咱们提供了Object.assign()办法,它从一个一个或多个对象中复制所有可枚举的属性值,并返回一个新对象。

然而,这也存在着一个问题,因为它不能正确的复制一个具备非默认属性值的属性。

如果对象只有一个setter,那么它就不会正确的复制到一个新对象上,应用Object.assign()进行如下操作:

const person1 = {    set name(newName) {        console.log(newName)    }}复制代码

这将不会起作用:

const person2 = {}Object.assign(person2, person1)复制代码

但这将会起作用:

const person3 = {}Object.defineProperties(person3,  Object.getOwnPropertyDescriptors(person1))复制代码

通过一个简略的console控制台,你能够查看以下代码:

person1.name = 'x'"x"person2.name = 'x'person3.name = 'x'"x"复制代码

person2没有setter,它没能复制进去,对象的浅复制限定也呈现在Object.create()办法中。

尾逗号

该个性容许在函数定义时有尾逗号,在函数应用时能够有尾逗号:

const doSomething = (var1, var2,) => {  //...}doSomething('test2', 'test2',)复制代码

该扭转将激励开发者进行“在一行开始时写逗号”的俊俏习惯

异步函数

JavaScript在很短的工夫内从回调函数进化到Promise函数(ES2015),并自从ES2017以来,异步JavaScript的async/wait语法变得更加简略。 异步函数是Promise和generator的联合,基本上,它是比Promise更高级的形象,我再反复个别:async/await是基于Promise建设的

为什么要引入async/await

它缩小了围绕promise的援用,并突破了Promise — “不要打断链式调用”的限度。

当Promise在ES2015中引入时,它的本意是来解决异步代码的问题,它也的确做到了,但在ES2015和ES2017距离的这两年中,大家意识到:_Promise不是解决问题的终极计划_。

Promise是为了解决驰名的_回调天堂_而被引入的,但它自身也带来了应用复杂性和语法复杂性。

Promise是很好的原生个性,围绕着它开发人员能够摸索出更好的语法,因而当时机成熟后,咱们失去了async函数

async函数使代码看起来像是同步函数一样,但其背地却是异步和非梗塞的。

它如何工作

一个async函数会返回一个promise,如下例:

const doSomethingAsync = () => {  return new Promise(resolve => {    setTimeout(() => resolve('I did something'), 3000)  })}复制代码

当你想要调用该函数时,你在后面加上了一个wait,这样调用就会被进行,直到该promise进行resolve或reject,需注意的是:外层函数必须定义为async,这是例子:

const doSomething = async () => {  console.log(await doSomethingAsync())}复制代码

一个上手示例

这是一个应用async/await进行异步函数的简略示例:

const doSomethingAsync = () => {  return new Promise(resolve => {    setTimeout(() => resolve('I did something'), 3000)  })}const doSomething = async () => {  console.log(await doSomethingAsync())}console.log('Before')doSomething()console.log('After')复制代码

下面的代码将会在浏览器的console中打印出如下后果:

BeforeAfterI did something //after 3s复制代码

对于 Promise

async 关键字标记在任何函数上,意味着这个函数都将返回一个 Promise,即便这个函数没有显式的返回,它在外部也会返回一个 Promise,这就是上面这份代码无效的起因:

const aFunction = async () => {  return 'test'}aFunction().then(alert) // This will alert 'test'复制代码

上面的例子也一样:

const aFunction = async () => {  return Promise.resolve('test')}aFunction().then(alert) // This will alert 'test'复制代码

更易于浏览的代码

正如上述的例子,咱们将它与一般回调函数或链式函数进行比拟,咱们的代码看起来十分的简略。

这是一个很简略的例子,当代码足够简单时,它会产生更多的收益。

例如,应用 Promise 来获取 JSON 资源并解析它:

const getFirstUserData = () => {  return fetch('/users.json') // get users list    .then(response => response.json()) // parse JSON    .then(users => users[0]) // pick first user    .then(user => fetch(`/users/${user.name}`)) // get user data    .then(userResponse => response.json()) // parse JSON}getFirstUserData()复制代码

这是应用 async/await 实现雷同性能的例子:

const getFirstUserData = async () => {  const response = await fetch('/users.json') // get users list  const users = await response.json() // parse JSON  const user = users[0] // pick first user  const userResponse = await fetch(`/users/${user.name}`) // get user data  const userData = await user.json() // parse JSON  return userData}getFirstUserData()复制代码

串行多个异步性能

async 函数非常容易,并且它的语法比 Promise 更易读。

const promiseToDoSomething = () => {  return new Promise(resolve => {    setTimeout(() => resolve('I did something'), 10000)  })}const watchOverSomeoneDoingSomething = async () => {  const something = await promiseToDoSomething()  return something + ' and I watched'}const watchOverSomeoneWatchingSomeoneDoingSomething = async () => {  const something = await watchOverSomeoneDoingSomething()  return something + ' and I watched as well'}watchOverSomeoneWatchingSomeoneDoingSomething().then(res => {  console.log(res)})复制代码

打印后果:

I did something and I watched and I watched as well复制代码

更简略的调试

调试 Promise 就很艰难,因为调试器无奈逾越异步代码,但调试 async/await 就十分的简略,调试器会像调试同步代码一样来解决它。

共享内存和原子

WebWorkers 能够在浏览器中创立多线程程序。

它们通过事件的形式来传递音讯,从 ES2017 开始,你能够应用 SharedArrayBuffer 在每一个 Worker 中和它们的创建者之间共享内存数组.

因为不晓得写入内存局部须要多长的周期来播送,因而在读取值时,任何类型的写入操作都会实现,Atomics 能够防止竞争条件的产生。

对于它的更多细节能够在proposal中找到。


这是 ES2017,接下来我将介绍 ES2018 的性能。


Rest/Spread Properties

ES2015 引入了解构数组的办法,当你应用时:

const numbers = [1, 2, 3, 4, 5][first, second, ...others] = numbers复制代码

and 开展参数:

const numbers = [1, 2, 3, 4, 5]const sum = (a, b, c, d, e) => a + b + c + d + econst sum = sum(...numbers)复制代码

ES2018 为对象引入了同样的性能。

解构:

const { first, second, ...others } = { first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }first // 1second // 2others // { third: 3, fourth: 4, fifth: 5 }复制代码

开展属性 容许通过组合在开展运算符之后传递的对象属性而创立新对象:

const items = { first, second, ...others }items //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }复制代码

异步迭代器

for-await-of 容许你应用异步可迭代对象做为循环迭代:

for await (const line of readLines(filePath)) {  console.log(line)}复制代码

因为它应用的了 await,因而你只能在 async 函数中应用它。

Promise.prototype.finally()

当一个 Promise 是 fulfilled 时,它会一个接一个的调用 then。

如果在这个过程中产生了谬误,则会跳过 then 而执行 catch

finally() 容许你运行一些代码,无论是胜利还是失败:

fetch('file.json')  .then(data => data.json())  .catch(error => console.error(error))  .finally(() => console.log('finished'))复制代码

正则表达式改良

ES2018 对正则表达式引入了许多改良,这些都能够在 flaviocopes.com/javascript-… 上找到。

以下是对于 ES2018 正则表达式改良的具体补充:

RegExp lookbehind assertions: 依据后面的内容匹配字符串

这是一个 lookahead: 你能够应用 ?= 来匹配字符串,前面追随一个特定的字符串:

/Roger(?=Waters)//Roger(?= Waters)/.test('Roger is my dog') //false/Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician') //true复制代码

?! 能够执行逆操作,如果匹配的字符串是no而不是在尔后追随特定的子字符串的话:

/Roger(?!Waters)//Roger(?! Waters)/.test('Roger is my dog') //true/Roger(?! Waters)/.test('Roger Waters is a famous musician') //false复制代码

Lookaheads 应用 ?= Symbol,它们曾经能够用了。

Lookbehinds, 是一个新性能应用?<=.

/(?<=Roger) Waters//(?<=Roger) Waters/.test('Pink Waters is my dog') //false/(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //true 复制代码

如果一个 lookbehind 是否定,那么应用 ?>!:

/(?<!Roger) Waters//(?<!Roger) Waters/.test('Pink Waters is my dog') //true/(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //false 复制代码

Unicode属性本义 p{…} and P{…}

在正则表达式模式中,你能够应用 d 来匹配任意的数字,s 来匹配任意不是空格的字符串,w 来匹配任意字母数字字符串,以此类推。

This new feature extends this concept to all Unicode characters introducing p{} and is negation P{}.

这个新性能扩大了unicode字符,引入了 p{} 来解决

任何 unicode 字符都有一组属性,例如 script 确认语言,ASCII 是一个布尔值用于查看 ASCII 字符。你能够将此属性方在() 中,正则表达式未来查看是否为真。

/^p{ASCII}+$/u.test('abc')   //✅/^p{ASCII}+$/u.test('ABC@')  //✅/^p{ASCII}+$/u.test('ABC?') //❌复制代码

ASCII_Hex_Digit 是另一个布尔值,用于查看字符串是否蕴含无效的十六进制数字:

/^p{ASCII_Hex_Digit}+$/u.test('0123456789ABCDEF') //✅/^p{ASCII_Hex_Digit}+$/u.test('h')                //❌复制代码

此外,还有很多其它的属性。你能够在()中增加它们的名字来查看它们,包含 Uppercase, Lowercase, White_Space, Alphabetic, Emoji等等:

/^p{Lowercase}$/u.test('h') //✅/^p{Uppercase}$/u.test('H') //✅/^p{Emoji}+$/u.test('H')   //❌/^p{Emoji}+$/u.test('??') //✅复制代码

除了二进制属性外,你还能够查看任何 unicode 字符属性以匹配特定的值,在这个例子中,我查看字符串是用希腊语还是拉丁字母写的:

/^p{Script=Greek}+$/u.test('') //✅/^p{Script=Latin}+$/u.test('hey') //✅复制代码

浏览github.com/tc39/propos… 获取应用所有属性的详细信息。

Named capturing groups

In ES2018 a capturing group can be assigned to a name, rather than just being assigned a slot in the result array:

const re = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/const result = re.exec('2015-01-02')// result.groups.year === '2015';// result.groups.month === '01';// result.groups.day === '02';复制代码

The s flag for regular expressions

The s flag, short for _single line_, causes the . to match new line characters as well. Without it, the dot matches regular characters but not the new line:

/hi.welcome/.test('hinwelcome') // false/hi.welcome/s.test('hinwelcome') // true复制代码

ESNext

什么是 ESNext ?

ESNext 是一个始终指向下一个版本 JavaScript 的名称。

以后的 ECMAScript 版本是 ES2018,它于2018年6月被公布。

历史上 JavaScript 标准化的版本都是在冬季被公布,因而咱们能够预期 ECMAScript 2019 将于 2019 年的冬季被公布。

所以在编写本文时 ES2018 曾经被公布,因而 ESNext 指的是 ES2019。

ECMAScript 规范的提案是分阶段组织的,第一到第三阶段属于功能性的孵化,第四阶段的性能才最终确定为新规范的一部分。

在编写本文时次要浏览器都实现了第四阶段大部分的性能,因而我将在本文中介绍它们。

其中一些变动次要在外部应用,但晓得产生了什么这也很好。

第三阶段还有一些其它性能,可能会在接下来的几个月内降级到第四阶段,你能够在这个 Github 仓库中查看它们:github.com/tc39/propos…。

Array.prototype.{flat,flatMap}

flat() 是一个新的数组实例办法,它能够将多维数组转化成一维数组。

例子:

['Dog', ['Sheep', 'Wolf']].flat()//[ 'Dog', 'Sheep', 'Wolf' ]复制代码

默认状况下它只能将二维的数组转化成一维的数组,但你能够增加一个参数来确定要开展的级别,如果你将这个参数设置为 Infinity 那么它将开展有限的级别到一维数组:

['Dog', ['Sheep', ['Wolf']]].flat()//[ 'Dog', 'Sheep', [ 'Wolf' ] ]['Dog', ['Sheep', ['Wolf']]].flat(2)//[ 'Dog', 'Sheep', 'Wolf' ]['Dog', ['Sheep', ['Wolf']]].flat(Infinity)//[ 'Dog', 'Sheep', 'Wolf' ]复制代码

如果你相熟数组的 map 办法,那么你就晓得应用它能够对数组的每个元素执行一个函数。

flatMap() 是一个新的数组实例办法,它将 flat()map 联合了起来,当你冀望在map函数中做一些解决时这十分有用,同时又心愿后果如同 flat

['My dog', 'is awesome'].map(words => words.split(' '))//[ [ 'My', 'dog' ], [ 'is', 'awesome' ] ]['My dog', 'is awesome'].flatMap(words => words.split(' '))//[ 'My', 'dog', 'is', 'awesome' ]复制代码

Optional catch binding

有时候咱们并不需要将参数绑定到 try/catch 中。

在以前咱们不得不这样做:

try {  //...} catch (e) {  //handle error}复制代码

即便咱们素来没有通过 e 来剖析谬误,但当初咱们能够简略的省略它:

try {  //...} catch {  //handle error}复制代码

Object.fromEntries()

Objects have an entries() method, since ES2017.

从 ES2017 开始 Object将有一个 entries() 办法。

它将返回一个蕴含所有对象本身属性的数组的数组,如[key, value]

const person = { name: 'Fred', age: 87 }Object.entries(person) // [['name', 'Fred'], ['age', 87]]复制代码

ES2019 引入了一个新的 Object.fromEntries() 办法,它能够从上述的属性数组中创立一个新的对象:

const person = { name: 'Fred', age: 87 }const entries = Object.entries(person)const newPerson = Object.fromEntries(entries)person !== newPerson //true复制代码

String.prototype.{trimStart,trimEnd}

这些性能曾经被 v8/Chrome 实现了近一年的工夫,它将在 ES2019 中实现标准化。

trimStart()

删除字符串首部的空格并返回一个新的字符串:

'Testing'.trimStart() //'Testing'' Testing'.trimStart() //'Testing'' Testing '.trimStart() //'Testing ''Testing'.trimStart() //'Testing'复制代码

trimEnd()

删除字符串尾部的空格并返回一个新的字符串:

'Testing'.trimEnd() //'Testing'' Testing'.trimEnd() //' Testing'' Testing '.trimEnd() //' Testing''Testing '.trimEnd() //'Testing'复制代码

Symbol.prototype.description

当初你能够应用 description 来获取 Symbol 的值,而不用应用 toString() 办法:

const testSymbol = Symbol('Test')testSymbol.description // 'Test'复制代码

JSON improvements

在此之前 JSON 字符串中不容许应用分隔符(u2028)和分隔符(u2029)。

应用 JSON.parse 时,这些字符会导致一个 SyntaxError 谬误,但当初它们能够正确的解析并如 JSON 规范定义的那样。

Well-formed JSON.stringify()

修复 JSON.stringify() 在解决 UTF-8 code points (U+D800 to U+DFFF)。

在修复之前,调用 JSON.stringify() 将返回格局谬误的 Unicode 字符,如(a "�")。

当初你能够平安释怀的应用 JSON.stringify() 转成字符串,也能够应用 JSON.parse() 将它转换回原始示意的状态。

Function.prototype.toString()

函数总会有一个 toString 办法,它将返回一个蕴含函数代码的字符串。

ES2019 对返回值做了批改,以防止剥离正文和其它字符串(如:空格),将更精确的示意函数的定义。

If previously we had

以前兴许咱们这样过:

function /* this is bar */ bar () {}复制代码

过后的行为:

bar.toString() //'function bar() {}复制代码

当初的行为:

bar.toString(); // 'function /* this is bar */ bar () {}'复制代码

总结一下,我心愿这篇文章能够帮忙你理解一些最新的 JavaScript 以及咱们在 2019 年行将看见的内容。

Click here to get a PDF / ePub / Mobi version of this post to read offline

Copyright

版权申明: 闪电矿工翻译组 译文仅用于学习、钻研和交换。版权归 闪电矿工翻译组、文章作者和译者所有,欢送非商业转载。转载前请分割译者或 管理员 获取受权,并在文章结尾显著地位注明本文出处、译者、校对者和闪电矿工翻译组的残缺链接,违者必究。