JavaScript 是一个不断发展的开发语言,在过去的几年中,ECMAScript(注:一个标准规范,JavaScript 是 ECMAScript 的实现和扩展)规范中添加了许多新功能。

本文摘自我的《Complete Guide to Modern JavaScript》一书的摘录,涵盖了ES2016,ES2017,ES2018,ES2019的新增内容。

如果你不想阅读繁琐的文字,可以直接跳到本文的结尾,你将看到一张图片,该图片总结了所有内容。

一,ES2016 中的新功能

1.1 数组实例的 includes()

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。

let array = [1,2,4,5];array.includes(2);// truearray.includes(3);// false

我们可以为.includes()提供索引以搜索目标元素。默认值为0,我们也可以传递一个负值。我们传入的第一个值是要搜索的目标元素,第二个是索引:

let array = [1,3,5,7,9,11];array.includes(3,1);// 从索引1处,开始查找值3// truearray.includes(5,4);//falsearray.includes(1,-1);// find the number 1 starting from the ending of the array going backwards// falsearray.includes(11,-3);// true

1.2 指数运算符

在ES2016之前,我们执行的是以下操作:

Math.pow(2,2);// 4Math.pow(2,3);// 8

现在,我们使用新增的指数运算符,可以这样子操作:

2**2;// 42**3;// 8

当结合以下示例中的多个操作时,它将非常有用:

2**2**2;// 16Math.pow(Math.pow(2,2),2);// 16

使用 Math.pow()你需要将它们连接起来,它可能会变得很长且混乱。指数运算符提供了一种更快,更干净的方法来完成相同的事情。

二,ES2017 中的新功能

ES2017引入了许多很棒的新功能。

2.1 String padding (.padStart() and .padEnd())

ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

"hello".padStart(6);// " hello":hello长度不足6,指定在头部补全空格"hello".padEnd(6);// "hello ":hello长度不足6,指定在尾部补全空格

padStart()padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。

"hello".padEnd(13," Alberto");// "hello Alberto""1".padStart(3,0);// "001""99".padStart(3,0);// "099"

2.2 Object.entries() and Object.values()

让我们先创建一个对象:

const family = {  father: "Jonathan Kent",  mother: "Martha Kent",  son: "Clark Kent",}

在以前的JavaScript版本中,我们将像这样访问对象内部的值:

Object.keys(family);// ["father", "mother", "son"]family.father;"Jonathan Kent"

Object.keys()仅返回对象的键,然后我们必须使用该键来访问值。现在,我们还有两种访问对象的方法:

Object.values(family);// ["Jonathan Kent", "Martha Kent", "Clark Kent"]Object.entries(family);// ["father", "Jonathan Kent"]// ["mother", "Martha Kent"]// ["son", "Clark Kent"]

Object.values()返回数组的所有值,而 Object.entries() 返回包含键和值的数组。

2.3 Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors() 方法,返回指定对象所有自身属性(非继承属性)的描述对象。

const myObj = {  name: "Alberto",  age: 25,  greet() {    console.log("hello");  },}Object.getOwnPropertyDescriptors(myObj);// age:{value: 25, writable: true, enumerable: true, configurable: true}// greet:{value: ƒ, writable: true, enumerable: true, configurable: true}// name:{value: "Alberto", writable: true, enumerable: true, configurable: true}

2.4 Trailing commas in function parameter lists and calls(函数参数的尾逗号)

这只是语法的一个小改动。现在,在编写对象时,无论是否为最后一个参数,我们都可以在其后留下逗号。

// from thisconst object = {  prop1: "prop",  prop2: "propop"}// to thisconst object = {  prop1: "prop",  prop2: "propop",}

请注意,我在第二个属性的末尾添加的一个逗号。如果你不加,它不会抛出任何错误,但是遵循这种规范是一种更好的做法,因为它可以使你的代码更清晰明了。

// I writeconst object = {  prop1: "prop",  prop2: "propop"}// my colleague updates the code, adding a new propertyconst object = {  prop1: "prop",  prop2: "propop"  prop3: "propopop"}// suddenly, he gets an error because he did not notice that I forgot to leave a comma at the end of the last parameter.

2.5 Shared memory and Atomics(Atomics 对象)

多线程共享内存,最大的问题就是如何防止两个线程同时修改某个地址,或者说,当一个线程修改共享内存以后,必须有一个机制让其他线程同步。SharedArrayBuffer API 提供Atomics对象,保证所有共享内存的操作都是“原子性”的,并且可以在所有线程内同步。关于更多详细,可以查看此文章

2.6 Async and Await

ES2017引入了一种新的promises方法,称为“async/await”。

2.6.1 Promise review

在深入探讨这种新语法之前,让我们快速回顾一下我们通常如何写一个Promise:

// fetch a user from githubfetch('api.github.com/user/AlbertoMontalesi').then( res => {  // return the data in json format  return res.json();}).then(res => {  // if everything went well, print the data  console.log(res);}).catch( err => {  // or print the error  console.log(err);})

这是一个非常简单的Promise,可以从 GitHub 获取用户并将其打印到控制台。

让我们看一个不同的例子:

function walk(amount) {  return new Promise((resolve,reject) => {    if (amount < 500) {      reject ("the value is too small");    }    setTimeout(() => resolve(`you walked for ${amount}ms`),amount);  });}walk(1000).then(res => {  console.log(res);  return walk(500);}).then(res => {  console.log(res);  return walk(700);}).then(res => {  console.log(res);  return walk(800);}).then(res => {  console.log(res);  return walk(100);}).then(res => {  console.log(res);  return walk(400);}).then(res => {  console.log(res);  return walk(600);});// you walked for 1000ms// you walked for 500ms// you walked for 700ms// you walked for 800ms// uncaught exception: the value is too small

让我们看看如何使用新的 async/await 语法重写此 Promise。

2.6.2 Async and Await
function walk(amount) {  return new Promise((resolve,reject) => {    if (amount < 500) {      reject ("the value is too small");    }    setTimeout(() => resolve(`you walked for ${amount}ms`),amount);  });}// create an async functionasync function go() {  // use the keyword `await` to wait for the response  const res = await walk(500);  console.log(res);  const res2 = await walk(900);  console.log(res2);  const res3 = await walk(600);  console.log(res3);  const res4 = await walk(700);  console.log(res4);  const res5 = await walk(400);  console.log(res5);  console.log("finished");}go();// you walked for 500ms // you walked for 900ms // you walked for 600ms // you walked for 700ms // uncaught exception: the value is too small

让我们一步一步分解,看看我们刚刚做的事情:

  • 要创建异步功能,我们需要将 async 关键字放在函数前面
  • async 关键字将告诉 JavaScript 始终返回 Promise
  • 如果我们指定返回 <non-promise>,它将返回包装在 Promise 中的值
  • await 关键字仅在异步函数内起作用
  • 顾名思义,await 将告诉 JavaScript 等待,直到 promise 返回其结果

让我们看看如果尝试在 async 函数之外使用 await 会发生什么:

// use await inside a normal functionfunction func() {  let promise = Promise.resolve(1);  let result = await promise; }func();// SyntaxError: await is only valid in async functions and async generators// use await in the top-level codelet response = Promise.resolve("hi");let result = await response;// SyntaxError: await is only valid in async functions and async generators
2.6.3 Error handling

我们通常使用 try...catch 捕获错误,但是在不使用 try...catch 的情况下,我们仍然可以用如下语法捕获错误:

async function asyncFunc(){  let response = await fetch('http:your-url');}asyncFunc().catch(console.log);// TypeError: Failed to fetch

三,ES2018 中的新功能

现在让我们看一下 ES2018 引入的新内容。

3.1 Rest / Spread(扩展运算符) for Objects

还记得ES6(ES2015)如何允许我们这样做吗?

const veggie = ["tomato","cucumber","beans"];const meat = ["pork","beef","chicken"];const menu = [...veggie, "pasta", ...meat];console.log(menu);// Array [ "tomato", "cucumber", "beans", "pasta", "pork", "beef", "chicken" ]

现在我们也可以对 Objects 使用 rest/spread 语法,让我们看看该如何使用:

let myObj = {  a:1,  b:3,  c:5,  d:8,}// we use the rest operator to grab everything else left in the object.let { a, b, ...z } = myObj;console.log(a);     // 1console.log(b);     // 3console.log(z);     // {c: 5, d: 8}// using the spread syntax we cloned our Objectlet clone = { ...myObj };console.log(clone);// {a: 1, b: 3, c: 5, d: 8}myObj.e = 15;console.log(clone)// {a: 1, b: 3, c: 5, d: 8}console.log(myObj)// {a: 1, b: 3, c: 5, d: 8, e: 15}

3.2 Asynchronous Iteration(异步遍历器)

异步遍历器,推荐阅读该文档

3.3 Promise.prototype.finally()

finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

const myPromise = new Promise((resolve,reject) => {  resolve();})myPromise.then( () => {    console.log('still working');    return 'still working';  })  .finally(()=> {    console.log('Done!');    return 'Done!';  })  .then( res => {    console.log(res);  })// still working// Done!// still working

3.4 正则的扩展

推荐阅读该文档

四,ES2019 中的新功能

让我们看一下 ECMAScript 最新版本(ES2019)中包含的内容:

4.1 数组实例的 flat(),flatMap()

Array.prototype.flat()将递归地将数组“拉平”到我们指定的深度。如果未指定 depth 参数,则默认值为1。我们可以使用 Infinity “拉平”所有嵌套数组。

const letters = ['a', 'b', ['c', 'd', ['e', 'f']]];// default depth of 1letters.flat();// ['a', 'b', 'c', 'd', ['e', 'f']]// depth of 2letters.flat(2);// ['a', 'b', 'c', 'd', 'e', 'f']// which is the same as executing flat with depth of 1 twiceletters.flat().flat();// ['a', 'b', 'c', 'd', 'e', 'f']// Flattens recursively until the array contains no nested arraysletters.flat(Infinity)// ['a', 'b', 'c', 'd', 'e', 'f']

Array.prototype.flatMap() 在处理“depth”参数方面与上一个相同,但是除了简单地“拉平”数组之外,我们还可以使用 flatMap() 映射它并以新的形式返回结果数组。

let greeting = ["Greetings from", " ", "Vietnam"];// let's first try using a normal `map()` functiongreeting.map(x => x.split(" "));// ["Greetings", "from"]// ["", ""]// ["Vietnam"]greeting.flatMap(x => x.split(" "))// ["Greetings", "from", "", "", "Vietnam"]

如上可见,如果我们使用 .map(),我们将得到一个多级数组,我们可以通过使用 .flatMap() 来将数组变平。

4.2 Object.fromEntries()

Object.fromEntries() 方法是 Object.entries() 的逆操作,用于将一个键值对数组转为对象。

const keyValueArray = [  ['key1', 'value1'],  ['key2', 'value2']]const obj = Object.fromEntries(keyValueArray)// {key1: "value1", key2: "value2"}

4.2 字符串的实例方法:trimStart(),trimEnd()

trimStart() 消除字符串头部的空格,trimEnd() 消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

let str = "    this string has a lot of whitespace   ";str.length;// 42str = str.trimStart();// "this string has a lot of whitespace   "str.length;// 38str = str.trimEnd();// "this string has a lot of whitespace"str.length;// 35

除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。

浏览器还部署了额外的两个方法,trimLeft()trimStart() 的别名,trimRight()trimEnd() 的别名。

4.3 catch 命令的参数省略

在 ES2019 之前,必须始终在 catch 子句中包含 error 参数。E2019 允许忽略它。

// Beforetry {   ...} catch(error) {   ...}// ES2019try {   ...} catch {   ...}

4.4 Function.prototype.toString()

ES2019 对函数实例的 toString() 方法做出了修改。.toString() 方法返回一个表示函数源代码的字符串。

function sum(a, b) {  return a + b;}console.log(sum.toString());// function sum(a, b) {//    return a + b;//  }

4.5 Symbol.prototype.description

创建 Symbol 的时候,可以添加一个描述。当我们要读取 Symbol 的描述时,可以使用 ES2019 提供的实例属性 description,直接返回 Symbol 的描述。

const me = Symbol("Alberto");me.description;// "Alberto"me.toString()//  "Symbol(Alberto)"

五,一图总结

原文:https://inspiredwebdev.com/everything-from-es-2016-to-es-2019

从 ECMAScript 2016 到 2019 你需要了解的新特性-野胡子的独立博客

参考