ECMAScript 2016,2017和2018中新增功能的示例

ECMAScript 2016,2017和2018中新增功能的示例

原文连接: medium.freecodecamp.org
翻译连接:https://www.zcfy.cc/article/h...html

始终紧跟Javascript(ECMAScript)的最新功能是很难的,更难的是找到有用的代码示例。git

所以,在本文中,我将介绍在TC39的最终提案中包含的18个功能,这些功能将被添加到ES2016, ES2017, and ES2018 (最终草案)中,并经过有实际用处的示例来展现它们。github

这是一篇很是长的贴子,但应该很是容易阅读。把它看成“Netflix狂欢阅读” 读完以后,我保证你将掌握大量有关这些功能的知识。web

让咱们一个一个地看看这些新功能正则表达式

1. Array.prototype.includes

includes是Array实例上一个很是简的方法,能够很是容易查找到某一项是否在Array中(包括 NaN 而不像 indexOf不能查找NaN)。数据库

const arr = [1, 2, 3, 4, NaN];
// Instead of
if (arr.indexOf(3) >= 0) {
    console.log(true);
}

// Use
if (arr.includes(3)) {
    console.log(true);
}

arr.includes(NaN); // true
arr.indexOf(NaN); // -1
花絮:JavaScript规范制定者想将它命名为contains,可是这个名字已经明确地被Mootools用了,所他们使用includes。

2. 指数 运算符

数学运算如加法和减法分别有+和- 等中缀运算符。与他们相似,中缀运算符一般用于指数运算。在ECMAScript 2016中,引入了来代替Math.pow。express

Math.pow(7,2) // 49

//Now
7**2 // 49

一、Object.values()
Object.values()是一个相似于Object.keys()的新函数,它返回对象自有属性的全部值,而不包括原型链上的任何值。数组

const cars = { BMW: 2, TESLA: 2, TOYOTA: 1 };

// ES2015
const vals = Object.keys(cars).map(key => cars[key]);
console.log(vals); // [3,2,1]

// ES2017
const values = Object.values(cars);
console.log(values);

二、 Object.entries()
Object.entries()与Object.keys有关,但不只仅返回键,而是以数组的形式返回键和值。这使得在循环中使用对象,或将对象转到Maps等变得很是简单。promise

示例 1:安全

const cars = { BMW: 2, TESLA: 2, TOYOTA: 1 };

// ES5.1
Object.keys(cars).forEach(function(key) {
    console.log('key: ' + key + 'value' + carsp[key]);
});

// ES8
for (let [key, value] of Object.entries(cars)) {
    console.log(`key: ${key} value:${value}`);
}

示例 2:

const cars = { BMW: 2, TESLA: 2, TOYOTA: 1 };

// ES2015
const map1 = new Map();
Object.keys(cars).forEach(key => {
    map1.set(key, cars[key]);
});

console.log(map1); // Map(3){"BMW" => 2, "TESLA" => 2, "TOYOTA" => 1}

// ES8
const map = new Map(Object.entries(cars));
console.log(map); // Map(3){"BMW" => 2, "TESLA" => 2, "TOYOTA" => 1}

3. 字符串填充

String新增了两个实例谢谢老婆---String.prototype.padStart 和 String.prototype.padEnd ,它们容许在原始字符串的开始和结尾处添加空字符串或者是其它的字符串。

'someString'.padStart(字符串的总长度 [,填充字符串]);

'5'.padStart(10) // '
5'

'5'.padStart(10, '=*') //'=*=*=*=*=5'
'5'.padEnd(10) // '5         '

'5'.padEnd(10, '=*') //'5=*=*=*=*='

当咱们想要漂亮的打印显示或终端打印等场景中对齐输出时,将变得的很方便。

3.1 padStart 示例:
在下面的示例中,有一些不一样长度的数字,咱们想在它们前面填充“0”,以便全部的项目都将以10位的相同长度用于显示。咱们可使用padStart(10, '0')很容易的实现。

// ECMAScript 2017
const formatted = [0, 1, 12, 123, 1234, 12345].map(num =>
    num.toString().padStart(10, '0')
);
console.log(formatted);// ["0000000000", "0000000001", "0000000012", "0000000123", "0000001234", "0000012345"]

3.2 padEnd 示例:
当咱们想右对齐打印多个不一样长度的项目时,使用padEnd就很是方便了。

下面是一个很是好实例,它展现了padEnd 、padStart 和 Object.entries如何结合使用产生一个漂亮的输出。

const cars = {

  '?BMW': '10',

  '?Tesla': '5',

  '?Lamborghini': '0'

}
Object.entries(cars).map(([name, count]) =>
{

  //padEnd appends '-' until the name becomes 20 characters

  //padStart prepends '0' until the count becomes 3 characters.

  console.log(`${name.padEnd(20, '-')} Count: ${count.padStart(3, '0')}`)

});
//Prints..

// ?BMW - - - - - - -  Count: 010

// ?Tesla - - - - - -  Count: 005

// ?Lamborghini - - -  Count: 000

3.3 ⚠️ Emojis和其余双字节字符上的padStart和padEnd
Emojis和其它双字节字符串使用unicode的多字节表示。因此padStart和padEnd可能没法按照预期的工做⚠️。

例如:假设咱们尝试用 ❤️表示符号填充字符中heart到10个字符,结果以下所示:

//Notice that instead of 5 hearts, there are only 2 hearts and 1 heart that looks odd!

'heart'.padStart(10, "❤️");
// prints.. '❤️❤️❤heart'

这是由于 ❤️是2个码长('u2764uFE0F')!单词heart是5个字符,因此我仅剩下5个字符用于填充。因此最终JS使用'u2764uFE0F'填充了两个心型❤️❤️。最一个❤的产生是由于JS仅使用了第一个字节u2764。

因此最后输出: ❤️❤️❤heart

PS:你可经过这个连接查看unicode字符转换。

4. Object.getOwnPropertyDescriptors

这个方法返回一个对象的全部属性的详细信息(包括get和set方法)。添加这个方法的主要目的是容许浅拷贝/克隆一个对象,包括对象上的getter和setter函数,不一样于Object.assign。

Object.assign 容许浅复制源对象上的全部属性的详细信息,除getter和setter函数外。

下面的示例经过拷贝一个原始对象Car到一个新对象ElectricCar,来展现Object.assign和Object.getOwnPropertyDescriptors以及Object.defineProperties之间的区别。你会发现使用Object.getOwnPropertyDescriptors,discount的getter和setter函数一样被拷贝到目标对象。

var Car = {

 name: 'BMW',

 price: 1000000,

 set discount(x) {

  this.d = x;

 },

 get discount() {

  return this.d;

 },

};
//Print details of Car object's 'discount' property

console.log(Object.getOwnPropertyDescriptor(Car, 'discount'));

//prints..

// { 

//   get: [Function: get],

//   set: [Function: set],

//   enumerable: true,

//   configurable: true

// }
//Copy Car's properties to ElectricCar using Object.assign

const ElectricCar = Object.assign({}, Car);
//Print details of ElectricCar object's 'discount' property

console.log(Object.getOwnPropertyDescriptor(ElectricCar, 'discount'));

//prints..

// { 

//   value: undefined,

//   writable: true,

//   enumerable: true,

//   configurable: true 


// }

//⚠️Notice that getters and setters are missing in ElectricCar object for 'discount' property !??
//Copy Car's properties to ElectricCar2 using Object.defineProperties 

//and extract Car's properties using Object.getOwnPropertyDescriptors

const ElectricCar2 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(Car));
//Print details of ElectricCar2 object's 'discount'
property

console.log(Object.getOwnPropertyDescriptor(ElectricCar2, 'discount'));

//prints..

// { get: [Function: get],  ??????

//   set: [Function: set],  ??????

//   enumerable: true,

//   configurable: true 

// }

// Notice that getters and setters are present in the ElectricCar2 object for 'discount' property!

5. 在函数参数中添加尾随逗号

这是一个小的更新,它容许咱们在函数最后一个参数的后面添加尾随逗号。为何这么作?它能够帮助像git blame这样的工具确认全部的责任都是新的开发者引发的。

下面的示例说明这个问题和解决方法。

图片描述

注意:你能够尾随逗号来调用函数。

6. Async/Await

若是你问我,我会说这个迄今为止最重要也是最有用的功能。异步函数容许们不再用处理回调地狱,同时使用整个代码看起来很简单。

async关键字告诉JavaScript编译器,以不一样的方式处理这个函数。在函数内执行到await关键字时,编译器将会暂停。它假设在等待以后的表达式返回一个承诺,并等待承诺解决或拒绝后继续执行。

在这个示例中,getAmount函数将调用两个异步的函数getUser 和 getBankBalance。咱们能够经过Promise实现,但使用async await会更加的优雅和简单。.

图片描述

6.1 异步函数自己返回一个Promise.
若是你想等待一个异步函数的返回结果,你须要你须要使用Promise’s then语法来捕获返回结果。

在下面的示例中,咱们想经过console.log来打印返回结果,而不是在doubleAndAdd函数内部。因此咱们想等函数返回结果并经过then语法将结果传递给console.log 。

图片描述

6.2 并行调用async/await
在上一个示例中,咱们调用了两次await语句,每一次等待1s(共2s)。In the previous example we are calling await twice, but each time we are waiting for one second (total 2 seconds). 相反,咱们可使用Promise.all并行执行两次调用,由于a和b。

图片描述

6.3 async/await函数的错误处理
使用async await时,有各类方法来处理错误。

选项 1 —在函数中使用try catch

//Option 1 - Use try catch within the function

async function doubleAndAdd(a, b) {

 try {

  a = await doubleAfter1Sec(a);

  b = await doubleAfter1Sec(b);

 } catch (e) {

  return NaN;
//return something

 }
return a + b;

}

//?Usage:

doubleAndAdd('one', 2).then(console.log);
// NaN

doubleAndAdd(1, 2).then(console.log);
// 6
function doubleAfter1Sec(param) {

 return new Promise((resolve, reject) =>
{

  setTimeout(function() {

   let val = param * 2;

   isNaN(val) ? reject(NaN) : resolve(val);

  }, 1000);

 });

}

Option 2— 捕获每一个一个 await 表达式
由于每个await表达式都返回一个Promise,你能够像下面展现在每一行上捕获错误。

//Option 2 - *Catch* errors on  every await line

//as each await expression is a Promise in itself

async function doubleAndAdd(a, b) {

 a = await doubleAfter1Sec(a).catch(e => console.log('"a" is NaN')); // ?

 b = await doubleAfter1Sec(b).catch(e => console.log('"b" is NaN')); // ?

 if (!a || !b) {

  return NaN;

 }

 return a + b;

}
//?Usage:

doubleAndAdd('one', 2).then(console.log);
// NaN  and logs:  "a" is NaN

doubleAndAdd(1, 2).then(console.log);
// 6
function doubleAfter1Sec(param) {

 return new Promise((resolve, reject) =>
{

  setTimeout(function() {

   let val = param * 2;

   isNaN(val) ? reject(NaN) : resolve(val);

  }, 1000);

 });

}

Option 3 — 捕获整个 async-await 函数

//Option 3 - Dont do anything but handle outside the function

//since async / await returns a promise, we can catch the whole function's error

async function doubleAndAdd(a, b) {

 a = await doubleAfter1Sec(a);

 b = await doubleAfter1Sec(b);

 return a + b;

}
//?Usage:

doubleAndAdd('one', 2)

 .then(console.log)

.catch(console.log); // ???`<------- use "catch"
function doubleAfter1Sec(param) {

 return new Promise((resolve, reject) =>`
{

  setTimeout(function() {

   let val = param * 2;

   isNaN(val) ? reject(NaN) : resolve(val);

  }, 1000);

 });

}
ECMAScript目前正在最张草案中,将于2018年6月或7月发布。 is currently in final draft and will be out in June or July 2018. 下文函数的全部功能都将在Stage-4中,并将成为ECMAScript 2018的一部分。

1. 共享内存和原子

这是一个伟大的、很是先进的功能,是对JS引擎的核心加强。

它的主要思想是想为JS提供一些多线程的功能,以便JS开发人员能够本身管理内存(代替JS引擎对内存的管理)来编写高性能的并发程序。

这是经过一种叫作SharedArrayBuffer的全局对象来实现的,它实质上是将数据存储在_共享_ 内存空间中。因此这个数据能够在JS主线程和web-worker之间共享。

到如今为止,若是咱们想在JS主线程和web-workers之间共享数据,只能先复制数据而后使用postMessage 发送到其它线程。

如今,只须要使用SharedArrayBuffer,主线程和多个web工做线程便可访问共享数据。

可是在线程之间共享内存会引起竞争条件的产生。为了防止条件的产生引入了“Atomics”全局对象。 当一个线程正在使用它的数据时,Atomics 提供了各类方法来锁定共享内存。 它还提供了安全地更新共享内存中的数据的方法。

建议经过某些库来使用这些功能,可是如今尚未构建在此功能上的库。

若是你有兴趣,推荐阅读:

From Workers to Shared Memor_y — _lucasfcosta
A cartoon intro to SharedArrayBuffers_ — _Lin Clark
Shared memory and atomics_ — _Dr. Axel Rauschmayer

2. 删除标签模板字符的限制

首先,咱们要弄清楚“Tagged Template literal”是什么,这样咱们才能更好的理解这个特性。

在ES2015+中,有一个叫标签模板的功能,它容许开发人员自定义如何插入字符串。例如,以标签方式插入字符串,以下所示... ...
图片描述

在标签中,能够写一个函数来接收字符串常量部分,如[ ‘Hello ‘, ‘!’ ]和要替换的变量,如[ 'Raja'], 把它们做为参数传入自定义的函数(如greet),而后从这个自定义的函数中返回任何所须要的内容。

下面的示例展现了咱们自定义的标签函数greet给某个时间附加如“Good Morning!”、“Good afternoon”这些字符串,取决于某个具体的时间并返回一个自定义的字符串。

图片描述

//A "Tag" function returns a custom string literal.

//In this example, greet calls timeGreet() to append Good //Morning/Afternoon/Evening depending on the time of the day.
function greet(hardCodedPartsArray, ...replacementPartsArray) {

 console.log(hardCodedPartsArray);
//[ 'Hello ', '!' ]

 console.log(replacementPartsArray);
//[ 'Raja' ]
let str = '';

 hardCodedPartsArray.forEach((string, i) =>
{

  if (i < replacementPartsArray.length) {

   str += `${string} ${replacementPartsArray[i] || ''}`;

  } else {

   str += `${string} ${timeGreet()}`;
//<-- append Good morning/afternoon/evening here

  }

 });

 return str;

}
//?Usage:

const firstName = 'Raja';

const greetings = greet`Hello ${firstName}!`;
//??<-- Tagged literal
console.log(greetings);
//'Hello  Raja! Good Morning!'
?
function timeGreet() {

 const hr = new Date().getHours();

 return hr < 12

  ? 'Good Morning!'

  : hr < 18 

  ? 'Good Afternoon!'

  : 'Good Evening!';

}

如今咱们讨论被看成标识名的函数究竟是什么,许多人但愿在不一样的场景中使用这个功能,例如在终端命令和HTTP请求的URI构成中,等。

⚠️The problem with Tagged String literal
问题是在ES2015和ES2016规范中不容许使用`“u” (unicode),“x”(hexadecimal) 等转义字符,除非它们看起来彻底像\u00A9 或 u{2F804} 或 xA9。

所以,若是你在一个标记函数内使用其它域的规则(如终端规则),那么可能须要使用ubla123abla这种规则,它看起来不像u0049 或 u{@F804},而后你将会获得一个语法错误。

在ES2018中,使用规则是很是宽松的,甚至无效的转义字符。只要标记函数经过对象返回属性值,如“cooked”(无效字符串是“undefined”)和“raw”(包括任何其它你想要的属性)。

function myTagFunc(str) { 

 return { "cooked": "undefined", "raw": str.raw[0] }

} 


var str = myTagFunc `hi \
  1. 正则表达式的“dotall”标志

在如今的RegEx中,尽管(“.”)应该匹配单个字符,便它不匹配会产生新的字符,像n r f等。

例如:

//Before

/first.second/.test('first\nsecond');
//false

这个加强的功能可使点运算符匹配任何单符。为了确保不破坏任何内容,咱们须要使用s标记来建立RegEx表示以使其能正常工做。

//ECMAScript 2018

/first.second/s.test('first\nsecond');
//true   Notice: /s ??

如下是(提案)[https://github.com/tc39/propo...] 文档中的所有API:

图片描述

4. RegEx命名的组捕获 ?

这个加强的功能模仿其它语言,如Python,Java等中带来一个很是有用的特性叫“命名组”。此特性容许开发人员编写RegExp时,为RegExp中的组的不一样部分提供格式为“(<name>...)”的名称(标识符)。 而后,他们可使用该名称轻松获取他们须要的任何组。

4.1 基本的命名组示例
在下面的示例中,咱们使用(?<year>) (?<month>) and (?year)名称对日期RegEx的不一样部分进行分组。 结果对象如今将包含一个具groups属性,它有year,month和year属性及相应的值。

4.2 在正则表达式自己内使用命名组
咱们可使用k <组名>这种格式在正则表达式自己反向引用该组。 下面的例子展现它的工做原理:

4.3 在String.prototype.replace中使用命名组
如今,命名组这个特性已经能够在字符串的实例方法replace中使用了,因此咱们能够很容易地替换字符串的单词。

例如,把“firstName, lastName”替换成“lastName, firstName”。

5. 对象的解构属性

解构操做符 ... (三个...)容许咱们提取对象中沿未提取的属性。

5.1 你可使用解构来只提取你想要的属性

5.2 更方便的是你能够删除你不想要的属 ??

6. 对象的属性扩展

扩展属性也是使用三个点 ...,解构属性,不一样的是使用扩展建立(从新构建)的是新的对象。

提醒:展开运算符用于等号的右侧,解构运算符用于等号的左侧。

7. RegExp Lookbehind Assertions

这是RegEx的一个加强的功能,它可使咱们确保某些字符串必定在其它的字符中以前。

您如今可使用一个组(?<= ...) (问号,小于,等于)来查找确定的断言。

此外,您可使用(?<!...)(问号,小于,感叹号)来查看否认断言。从本质上讲,只要-ve断言经过,就会匹配。

确定的断言: 好比说,咱们但愿确保#在单词winning以前(即:#winning),并但愿正则表达之返回字符串“winning”,下面展现它的写法。

否认的断言: 比方说,咱们但愿获取€而不是$字符以后的数字。

8. RegExp Unicode 转义

要编写RegEx来匹配各类Unicode字符是很难的。诸如 w, W, d等只能匹配英文字符和数字。 可是对于印度语,希腊语等其余语言的数字呢?

这就是Unicode Property Escapes的用武之地。结果是,Unicode为每一个符号(字符)添加了元数据属性,并使用它来对各类符号进行分组或表征。

例如,Unicode数据库将全部印地语字符(हिन्दी)分组在一个key为Script、值为Devanagari的属性下,另外一个key为Script_Extensions的属性值也为Devanagari。因此咱们能够搜索Script = Devanagari并得到全部的印地文字符。

` Devanagari 可用于马拉地语,北印度语,梵语等各类印度语言。

从ECMAScript 2018开始,咱们可使用 p转义字符和{Script = Devanagari}来匹配全部印度字符。 也就是说,咱们能够在RegEx中使用: p {Script = Devanagari} 来匹配全部Devanagari字符。

//The following matches multiple hindi character

/^p{Script=Devanagari}+$/u.test('हिन्दी');
//true

//PS:there are 3 hindi characters h
一样,Unicode数据库将全部希腊字符组合为Script_Extensions(和Script)属性,其值为Greek。 因此咱们可使用Script_Extensions = Greek或Script = Greek来搜索全部希腊字符。

也就是说,咱们能够在RegEx中使用: p {Script = Greek} 以匹配全部Greek字符。

//The following matches a single Greek character

/p{Script_Extensions=Greek}/u.test('π'); // true
此外,Unicode数据库将各类类型的表情符号存储在属性值为“真”的布尔属性Emoji,Emoji_Component,Emoji_Presentation,Emoji_Modifier和Emoji_Modifier_Base下。因此咱们能够经过简单的选择“Emoji”来搜索全部的表情符号。

也就是说,咱们可使用: p {Emoji} , Emoji_Modifier 等来匹配各类表情符号。

下面的例子将会说明的更清晰一点。

//The following matches an Emoji character

/\p{Emoji}/u.test('❤️');
//true
//The following fails because yellow emojis don't need/have Emoji_Modifier!

/\p{Emoji}\p{Emoji_Modifier}/u.test('✌️');
//false
//The following matches an emoji character\p{Emoji} followed by \p{Emoji_Modifier}

/\p{Emoji}\p{Emoji_Modifier}/u.test('✌?');
//true
//Explaination:

//By default the victory emoji is yellow color.

//If we use a brown, black or other variations of the same emoji, they are considered

//as variations of the original Emoji and are represented using two unicode characters.

//One for the original emoji, followed by another unicode character for the color.

//

//So in the below example, although we only see a single brown victory emoji,

//it actually uses two unicode characters, one for the emoji and another

// for the brown color.

//

//In Unicode database, these colors have Emoji_Modifier property.

//So we need to use both \p{Emoji} and \p{Emoji_Modifier} to properly and

//completely match the brown emoji.

/\p{Emoji}\p{Emoji_Modifier}/u.test('✌?');
//true

最后,咱们可使用大写的“P”( P )转义字符而不是小p( p)来否认匹配。

参考:
ECMAScript 2018 Proposal
https://mathiasbynens.be/note...

  1. Promise.prototype.finally()

finally() 是Promise新添加的实例方法。主要想法是容许在“resolve”或“reject”以后运行回调来处理其它逻辑。 finally **回调被调用时没有任何价值,而且不管如何老是被执行。

看看不一样的状况。

9. 异步迭代

这是一个很是有用的功能。 基本上它容许咱们轻松建立异步代码循环!

这个特性增长了一个新的“for-await-of”循环,容许咱们调用异步函数来返回承诺(或带有一系列承诺的数组)。比较的酷的事情是,在下一个循环执行以前当前循环都会等待全部Promise返回承诺。
图片描述

相关文章
相关标签/搜索