乐趣区

关于前端:ES5-to-ESNext-自-2015-以来-JavaScript-新增的所有新特性

  • 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 = 1
var 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 = `Hey
this
string
is awesome!`
复制代码

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

const string = `First
                Second`
复制代码

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

First
                Second
复制代码

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

const string = `
First
Second`.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 = `something
another ${'x'}
new line ${1 + 2 + 3}
test`
复制代码

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

;`something
another `
复制代码

第 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 value
for (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 = true
const 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)
})
// or
new 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 = 1
const b = 2
const c = 3
export {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 //Fiesta
copied.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__ = animal
mammal.isAnimal //true
const dog = Object.create(animal)
dog.isAnimal  //true
console.log(dog.isMammal)  //undefined
Object.setPrototypeOf(dog, mammal)
dog.isAnimal //true
dog.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 + e
const sum = sum(...numbers)
复制代码

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

残余属性(Rest properties):

const {first, second, ...others} = {
  first: 1,
  second: 2,
  third: 3,
  fourth: 4,
  fifth: 5
}
first // 1
second // 2
others // {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') //true
s.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()]
// or
const 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']) //red
console.log(car.color) //red
console.log(car.owner) //Flavio
console.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) //test
const 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 中打印出如下后果:

Before
After
I 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 + e
const sum = sum(...numbers)
复制代码

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

解构:

const {first, second, ...others} = {first: 1, second: 2, third: 3, fourth: 4, fifth: 5}
first // 1
second // 2
others // {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

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

退出移动版