JavaScript
是一个不断发展的开发语言,在过去的几年中,ECMAScript
(注:一个标准规范,JavaScript 是 ECMAScript 的实现和扩展)规范中添加了许多新功能。
如果你不想阅读繁琐的文字,可以直接跳到本文的结尾,你将看到一张图片,该图片总结了所有内容。
一,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)"
五,一图总结
参考
- http://es6.ruanyifeng.com/#README