ES5 to ESNext —  自 2015 以来 JavaScript 新增的全部新特性



ES5 to ESNext —  自 2015 以来 JavaScript 新增的全部新特性

img

这篇文章的出发点是为了帮助前端开发者串联 ES6先后的 JavaScript 知识,而且能够快速了解 JavaScript 语言的最新进展。javascript

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

JavaScript 在将来有着极好的发展空间,跟上它的变化不会比如今更加的困难。个人目标是让你可以快速且全面的了解这门语言可使用的新内容。前端

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

目录

ECMAScript 简介node

ES2015react

ES2016git

ES2017github

ES2018web

ESNext正则表达式

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 来定义版本号。

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

img

接下来,咱们来深刻了解 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 line\n \ second line'
复制代码

或者

const string = 'first line\n' + '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、一、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中,好比:

在现代的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 字符组合起来:

"\ud842\udfb7" //"𠮷"
复制代码

你也能够用 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等其它多种语言)。

img


这些都是2016年引入的特性,如今让咱们进入2017年。


字符串填充

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

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

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

使用例子:

img

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('hi\nwelcome') // false
/hi.welcome/s.test('hi\nwelcome') // 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

版权声明: 闪电矿工翻译组 译文仅用于学习、研究和交流。版权归 闪电矿工翻译组、文章做者和译者全部,欢迎非商业转载。转载前请联系译者或 管理员 获取受权,并在文章开头明显位置注明本文出处、译者、校对者和闪电矿工翻译组的完整连接,违者必究。

相关文章
相关标签/搜索