很难追踪 JavaScript(ECMAScript)中的新功能。 想找到有用的代码示例更加困难。html
所以,在本文中,我将介绍 TC39 已完成 ES2016,ES2017 和 ES2018(最终草案)提案中全部添加的 18 个功能,并展现有用的示例。前端
这是一个很长的文章,但应该很容易阅读。 能够把它想象成 “Netflix binge reading”。可是到文章结束,我保证你将对全部这些功能有很好的了解。git
好的,让咱们逐个讨论这些问题。github
includes
是 Array 上的一个简单实例方法,有助于轻松查找某项元素是否在数组中(包括NaN
,与 indexOf
不一样)。web
人们想要 contains
来命名该规范,但显然 Mootools 已经使用过这个命名,因此使用了 includes
。正则表达式
更多示例和常见问题请参见:ES2016 新特性:npm scripts : 每一个前端开发都应知道的一些使用提示数据库
加法和减法等数学运算分别具备 +
和 -
等中缀运算符。与它们相似,**
中缀运算符一般用于指数运算。在 ECMAScript 2016 中,引入了 **
代替 Math.pow
。express
更多示例和常见问题请参见:ES2016 新特性:求幂运算符(**)npm
Object.values()
是一个与 Object.keys()
相似的新函数,但返回 Object 自身属性的全部值,不包括原型链中的任何值。数组
更多示例和常见问题请参见:ES2017 新特性:Object.entries() 和 Object.values()
Object.entries()
与 Object.keys
相关,但它不只仅返回 keys ,而是以数组方式返回 keys 和 values 。这使得在循环中使用对象或将对象转换为 Maps 等操做变得很是简单。
示例1
示例2
更多示例和常见问题请参见:ES2017 新特性:Object.entries() 和 Object.values()
String 中添加了两个实例方法,String.prototype.padStart
和 String.prototype.padEnd
– 容许将空字符串或其余字符串附加到原始字符串的开头或结尾。
'someString'.padStart(numberOfCharcters [,stringForPadding]); '5'.padStart(10) // ' 5' '5'.padStart(10, '=*') //'=*=*=*=*=5' '5'.padEnd(10) // '5 ' '5'.padEnd(10, '=*') //'5=*=*=*=*='
当咱们想要对齐字符串的长度的时候,能够很是方便的使用这两个函数。
在下面的示例中,咱们列出了不一样长度的数字。咱们但愿前置“0”,以便全部项具备相同的 10 位数长度显示。咱们可使用 padStart(10, '0')
轻松实现这一目标。
当咱们打印不一样长度的多个项并但愿正确对齐它们时,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
Emojis 和其余双字节字符使用多个字节的 unicode 表示。 因此 padStart
和 padEnd
可能没法按预期工做!⚠️
例如:假设咱们把字符串 heart
经过 emoji表情 ❤️ 使用 padStart
延长到十个字节,这个时候咱们获得以下的输出:
这是由于 ❤️ 自己占据两个字节(\u2764\uFE0F
),而 heart
自己有 5 个字节,因此咱们只剩 5 个字节的位置能够填充,JS使用\u2764\uFE0F
来填充两颗心并产生 ❤️❤️ 。对于最后一个,它只使用 heart \u2764
的第一个字节产生 ❤
因此咱们最终获得:❤️❤️❤heart
PS:你可使用 此连接 查看 unicode 字符转换。
更多示例和常见问题请参见:ES2017 新特性:字符串方法:padStart 和 padEnd
此方法返回给定对象的全部属性的全部详细信息(包括 getter get
和 setter set
方法)。 添加它的主要动机是容许浅复制/克隆对象到另外一个对象,该对象也复制 getter 和 setter 函数而不像 Object.assign
。
Object.assign
浅复制除原始源对象的 getter
和 setter
函数以外的全部信息。
下面的示例显示了 Object.assign
和 Object.getOwnPropertyDescriptors
以及 Object.defineProperties
之间的区别,以将原始对象 Car
复制到新对象 ElectricCar
中。 你将看到,经过使用 Object.getOwnPropertyDescriptors
,discount
getter 和 setter 函数也会复制到目标对象中。
以前…
之后…
var Car = { name: 'BMW', price: 1000000, set discount(x) { this.d = x; }, get discount() { return this.d; }, }; // 打印 Car 对象 'discount' 属性的详细信息 console.log(Object.getOwnPropertyDescriptor(Car, 'discount')); // 打印 .. // { // get: [Function: get], // set: [Function: set], // enumerable: true, // configurable: true // } //使用 Object.assign 将 Car 的属性复制到 ElectricCar const ElectricCar = Object.assign({}, Car); // 打印 ElectricCar 对象 'discount' 属性的详细信息 console.log(Object.getOwnPropertyDescriptor(ElectricCar, 'discount')); // 打印 .. // { // value: undefined, // writable: true, // enumerable: true, // configurable: true // } //⚠️ 请注意,ElectricCar 对象中的 'discount' 属性缺乏 getter 和 setter !👎👎 // 使用 Object.defineProperties 将 Car 的属性复制到 ElectricCar2 , // 并使用 Object.getOwnPropertyDescriptors 提取 Car的属性 const ElectricCar2 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(Car)); // 打印 ElectricCar2 对象的 'discount' 属性的详细信息 console.log(Object.getOwnPropertyDescriptor(ElectricCar2, 'discount')); // 打印.. // { get: [Function: get], 👈🏼👈🏼👈🏼 // set: [Function: set], 👈🏼👈🏼👈🏼 // enumerable: true, // configurable: true // } // 请注意,ElectricCar2 对象中的 'discount' 属性存在 getter 和 setter !
更多示例和常见问题请参见:ES2017 新特性:Object.getOwnPropertyDescriptors()
这是一个小更新,容许咱们在函数最后一个参数后面有逗号。 为何? 帮助使用像 git blame 这样的工具,防止添加一个参数却须要修改两行代码。
如下示例显示了问题和解决方案。
注意:你也能够在调用函数时使用尾逗号!
更多示例和常见问题请参见:ES2017 新特性:函数参数列表和调用尾逗号
到目前为止,这个特性应该是目前为止是最重要和最有用的功能。async 函数解决了回调地狱的问题,并使整个代码看起来简单。
async
关键字告诉 JavaScript 编译器以不一样方式处理函数。 只要到达该函数中的 await
关键字,编译器就会暂停。 它假定 await
以后的表达式返回一个 promise
并等待,直到 promise 被 resolved 或被 rejected ,而后才继续执行。
在下面的示例中,getAmount
函数调用两个异步函数 getUser
和 getBankBalance
。 咱们能够用 Promise 作到这一点,可是使用 async await
更加优雅和简单。
若是你正在等待 async 函数的结果,则须要使用 Promise 的 then
语法来捕获其结果。
在如下示例中,咱们但愿使用 console.log
但不在 doubleAndAdd
中记录结果。 因此咱们想等待并使用 then
语法将结果传递给console.log
。
在前面的例子中,咱们调用 await 两次,但每次咱们等待一秒钟(总共2秒)。相反,咱们能够并行调用它,由于使用 Promise.all
并行调用 a
和 b
。
使用 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); }); }
选项2-捕获(Catch) await 表达式
因为每个 await
表达式返回的都是 Promise,咱们能够直接在每一行上面添加 catch。
//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); }); }
选项3-捕获(Catch) 整个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); }); }
这是一个巨大的,很是先进的功能,而且是对 JS 引擎的核心加强。
这个特性的主要目的是给 JavaScript 提供多线程功能,以便JS开发人员经过本身管理内存来编写高性能的并发程序,而不是让JS引擎管理内存。
这是经过一种名为 SharedArrayBuffer 的新型全局对象完成的,该对象实质上将数据存储在共享内存空间中。所以,这些数据能够在主JS线程和 Web-worker 线程之间共享。
以前,若是咱们想在主 JS 线程和 web-worker 之间共享数据,咱们必须复制数据并使用 postMessage
将其发送到另外一个线程。
如今,你只需使用 SharedArrayBuffer
,主线程和多个 web-worker 线程均可以当即访问数据。
可是在线程之间共享内存会致使竞争条件(即多个进程同时操做一个内存)。为了帮助避免竞争条件,引入了 Atomics 全局对象。 Atomics 提供了各类方法来在线程使用其数据时锁定共享内存。它还提供了安全地更新共享内存中的此类数据的方法。
建议经过某个库使用此功能,可是如今没有基于此功能构建的库。
若是你有兴趣,我建议阅读:
首先,咱们须要澄清“标记模板字面量”是什么,以便咱们更好地理解这个功能。
在 ES2015+ 中,有一个称为标记模板文字的功能,容许开发人员自定义字符串的插值方式。 例如,在标准方式中,字符串被插入以下…
在标记的字面量中,你能够编写一个函数来接收字符串字面量的硬编码部分,例如 ['Hello','!']
而且替换变量,例如 ['Raja']
,做为参数进入一个自定义函数(例如 greet
),并从该自定义函数返回任何你想要的内容。
下面的示例显示咱们的自定义 “Tag” 函数 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!'; }
如今咱们讨论了 “Tagged” 函数是什么,许多人想要在不一样的场景下中使用此功能,例如在终端中使用命令和 HTTP 请求来编写 URI ,等等。
⚠️标签字符串模版存在的问题
ES2015 和 ES2016 规范不容许使用转义字符,如 \u
(unicode),\x
(十六进制),除非它们看起来彻底像 \u00A9
或 \u{2F804}
或 \xA9
。
所以,若是你有一个内部使用其余域规则(如终端规则)的 Tagged 函数,可能须要使用 \ubla123abla
,而不能是 \u0049
或 \u{@F804}
,这样你会获得一个语法错误。
在 ES2018 中,只要 Tagged 函数返回具备 “cooked” 属性(无效字符为 “undefined” )的对象中的值,而后是 “raw” 属性( 不管你想要什么)。
function myTagFunc(str) { return { "cooked": "undefined", "raw": str.raw[0] } } var str = myTagFunc `hi \ubla123abla`; //call myTagFunc str // { cooked: "undefined", raw: "hi \\unicode" }
目前在RegEx中,点(“.”)能够表示任何的单一字符,但它不能与 \n
, \r
,\f
等换行符匹配。 例如:
//Before /first.second/.test('first\nsecond'); //false
此加强功能使点运算符能够匹配任何单个字符。为了确保不会破坏任何内容,咱们须要在建立RegEx时使用 \s
标记才能使其正常工做。
//ECMAScript 2018 /first.second/s.test('first\nsecond'); //true Notice: /s 👈🏼
如下是 提案 文档中的总体API:
这个加强功能带来了其余语言(如Python,Java等)的有用 RegExp 功能,称为“命名组”。这个功能容许容许正则表达式给每个捕获组起一个名字 (?<name>...)
,而后,咱们可使用该名称轻松获取咱们须要的任何群组。
在下面的例子中,咱们使用 (?<year>)
(?<month>)
(?<day>)
来为正则表达式中的不一样部分分组,结果对象中会包含一个 groups
属性,其拥有 year
month
day
三个对象。
咱们可使用 \k<group name>
格式来反向引用正则表达式自己中的组。如下示例显示了它的工做原理。
命名组也能够在 String 的 replace 方法中使用,好比用来交换一个字符串中各个部分的位置。
例如,将firstName, lastName
更改成 lastName, firstName
。
Rest 运算符 ...
(三个点)容许咱们提取 Object 的剩余属性。
展开属性看起来就像 Rest 运算符,都是三个点 ...
,但不一样之处在于你使用展开操做符来建立(重构)新对象。
提示:展开(spread)运算符用于等号的右侧。剩余(Rest)运算符用在等号的左侧。
这是 RegEx 的一个加强,它容许咱们确保某些子字符串刚好出如今某些子字符串以前。
你如今可使用一个组 (?<=…)
(问号,小于,等于)来查看先行断言。
此外,你可使用 (?<!…)
(问号,小于,感叹号)来查看后行断言。基本上,只要-ve断言经过,这将匹配。
确定断言:假设咱们要确保 #
符号存在于 winning
以前(即:#winning
),并但愿正则表达式只返回字符串 “winning” 。下面是咱们的作法:
否认断言:假设咱们想要从具备 €
符号的行中提取数字,而不是 $
。
提案连接:https://github.com/tc39/proposal-regexp-unicode-property-escapes
编写 RegEx 以匹配各类 unicode 字符并不容易。像 \w
,\W
,\d
等只匹配英文字符和数字。可是其余语言中的数字如印地语,希腊语等等该怎么办呢?
这就是 Unicode 属性转义的用武之地。事实证实,Unicode 为每一个符号(字符)添加元数据属性,并使用它来分组或表征各类符号。
例如,Unicode 数据库将全部印地语字符(??????)归为一个名为 Script
的属性,其值为 Devanagari
,另外一个属性为Script_Extensions
,其值为 Devanagari
。因此咱们能够搜索 Script=Devanagari
并得到全部印地语字符。
梵文能够用于各类印度语言,如马拉地语,印地语,梵语等。
从 ECMAScript 2018 开始,咱们可使用 \p
来转义字符以及 {Script = Devanagari}
以匹配全部这些印度字符。也就是说,咱们能够在 RegEx 中使用:\p{Script=Devanagari}
来匹配全部梵文字符。
一样,Unicode 数据库将 Script_Extensions
(和 Script
)属性下的全部希腊字符组合为希腊语。 因此咱们可使用 Script_Extensions=Greek
或 Script=Greek
搜索全部希腊字符。
也就是说,咱们能够在RegEx中使用: \p{Script=Greek}
来匹配全部希腊字符。
此外,Unicode数据库在布尔属性 Emoji
,Emoji_Component
, Emoji_Presentation
,Emoji_Modifier
和 Emoji_Modifier_Base
下存储各类类型的 Emojis,其属性值为 true
。 所以,咱们只需选择 Emoji 符号便可搜索全部表情符号。
也就是说,咱们可使用:\p{Emoji}
,\Emoji_Modifier
等来匹配各类 Emojis 。
如下示例将使一切清楚。
//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
)来否认匹配。
参考:
finally()
是一个添加到 Promise 实例的新方法。 主要考虑是容许在 resolve 或 reject 调用以后执行一些清理性质的代码。finally 被执行的时候不会被传入任何函数,而且不管何时都会被执行。
咱们来看看各类状况。
这是一个很是有用的特性。 基本上它容许咱们轻松建立异步代码循环!
此特性添加了一个新的“for-await-of”循环,容许咱们在循环中调用返回 promises(或带有一堆 promise 的 Arrays )的异步函数。 循环会等待每一个 Promise 在进行下一个循环以前 resolve 。