ES7、ES8、ES9、ES10 新特性
ES7 新特性
1.Array.prototype.includes()方法
在 ES6 中我们有 String.prototype.includes() 可以查询给定字符串是否包含一个字符,而在 ES7 中,我们在数组中也可以用 Array.prototype.includes 方法来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。
1 | const arr = [1, 3, 5, 2, "8", NaN, -0]; |
在 ES7 之前想判断数组中是否包含一个元素,有如下两种方法,但都不如 includes 来得直观:
- indexOf()
indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
1 | if (arr.indexOf(el) !== -1) { |
不过这种方法有两个缺点,一是不够语义化,要先找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对 NaN 的误判。
1 | [NaN].indexOf(NaN); // -1 |
- find() 和 findIndex()
数组实例的 find 方法,用于找出第一个符合条件的数组成员。另外,这两个方法都可以发现 NaN,弥补了数组的 indexOf 方法的不足。
1 | [1, 4, -5, 10] |
Array.prototype.includes()的支持情况:
2.求幂运算符**
在 ES7 中引入了指数运算符,具有与 Math.pow()等效的计算结果
1 | console.log(2 ** 10); // 输出1024 |
求幂运算符的支持情况:
ES8 新特性
1.Async/Await
我们都知道使用 Promise 能很好地解决回调地狱的问题,但如果处理流程比较复杂的话,那么整段代码将充斥着 then,语义化不明显,代码不能很好地表示执行流程,那有没有比 Promise 更优雅的异步方式呢?
假如有这样一个使用场景:需要先请求 a 链接,等返回信息之后,再请求 b 链接的另外一个资源。下面代码展示的是使用 fetch 来实现这样的需求,fetch 被定义在 window 对象中,它返回的是一个 Promise 对象
1 | fetch("https://blog.csdn.net/") |
虽然上述代码可以实现这个需求,但语义化不明显,代码不能很好地表示执行流程。基于这个原因,ES8 引入了 async/await,这是 JavaScript 异步编程的一个重大改进,提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力,并且使得代码逻辑更加清晰。
1 | async function foo() { |
通过上面代码,你会发现整个异步处理的逻辑都是使用同步代码的方式来实现的,而且还支持 try catch 来捕获异常,这感觉就在写同步代码,所以是非常符合人的线性思维的。需要强调的是,await 不可以脱离 async 单独使用,await 后面一定是 Promise 对象,如果不是会自动包装成 Promise 对象。
根据 MDN 定义,async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。
1 | async function foo() { |
上述代码,我们可以看到调用 async 声明的 foo 函数返回了一个 Promise 对象,等价于下面代码:
1 | async function foo() { |
Async/Await 的支持情况:
2.Object.values(),Object.entries()
ES5 引入了 Object.keys 方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。ES8 引入了跟 Object.keys 配套的 Object.values 和 Object.entries,作为遍历一个对象的补充手段,供 for…of 循环使用。
Object.values 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
1 | const obj = { foo: "bar", baz: 42 }; |
需要注意的是,如果属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是 b、c、a。
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。这个特性我们后面介绍 ES10 的 Object.fromEntries()还会再提到。
1 | const obj = { foo: "bar", baz: 42 }; |
Object.values()与 Object.entries()兼容性一致,下面以 Object.values()为例:
3.String padding
在 ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。我们先看下使用语法:
1 | String.padStart(targetLength, [padString]); |
- targetLength(必填):当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
- padString(可选):填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为 “ “。
1 | "x".padStart(4, "ab"); // 'abax' |
有时候我们处理日期、金额的时候经常要格式化,这个特性就派上用场:
1 | "12".padStart(10, "YYYY-MM-DD"); // "YYYY-MM-12" |
String padding 的支持情况:
4.Object.getOwnPropertyDescriptors()
ES5 的 Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象(descriptor)。ES8 引入了 Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。
得到的结果:
该方法的引入目的,主要是为了解决 Object.assign()无法正确拷贝 get 属性和 set 属性的问题。
上面代码中,source 对象的 foo 属性的值是一个赋值函数,Object.assign 方法将这个属性拷贝给 target1 对象,结果该属性的值变成了 undefined。这是因为Object.assign 方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。
这时,Object.getOwnPropertyDescriptors()方法配合 Object.defineProperties()方法,就可以实现正确拷贝。
Object.getOwnPropertyDescriptors()的支持情况:
ES9 新特性
1.for await of
for of 方法能够遍历具有 Symbol.iterator 接口的同步迭代器数据,但是不能遍历异步迭代器。
ES9 新增的 for await of 可以用来遍历具有 Symbol.asyncIterator 方法的数据结构,也就是异步迭代器,且会等待前一个成员的状态改变后才会遍历到下一个成员,相当于 async 函数内部的 await。现在我们有三个异步任务,想要实现依次输出结果,该如何实现呢?
1 | // for of遍历 |
得到如下结果:
上述代码证实了 for of 方法不能遍历异步迭代器,得到的结果并不是我们所期待的,于是 for await of 就粉墨登场啦!
1 | function Gen(time) { |
使用 for await of 遍历时,会等待前一个 Promise 对象的状态改变后,再遍历到下一个成员。
异步迭代器的支持情况:
2.Object Rest Spread
ES6 中添加的最意思的特性之一是 spread 操作符。你不仅可以用它替换 cancat()和 slice()方法,使数组的操作(复制、合并)更加简单,还可以在数组必须以拆解的方式作为函数参数的情况下,spread 操作符也很实用。
1 | const arr1 = [10, 20, 30]; |
ES9 通过向对象文本添加扩展属性进一步扩展了这种语法。他可以将一个对象的属性拷贝到另一个对象上,参考以下情形:
1 | const input = { |
上面代码可以把 input 对象的数据都添加到 output 对象中,需要注意的是,如果存在相同的属性名,只有最后一个会生效。
1 | const input = { |
上面例子中,修改 input 对象中的值,output 并没有改变,说明扩展运算符拷贝一个对象(类似这样 obj2 = {…obj1}),实现只是一个对象的浅拷贝。值得注意的是,如果属性的值是一个对象的话,该对象的引用会被拷贝:
1 | const obj = { x: { y: 10 } }; |
copy1.x 和 copy2.x 指向同一个对象的引用,所以他们严格相等。
我们再来看下 Object rest 的示例:
1 | const input = { |
当对象 key-value 不确定的时候,把必选的 key 赋值给变量,用一个变量收敛其他可选的 key 数据,这在之前是做不到的。注意,rest 属性必须始终出现在对象的末尾,否则将抛出错误。
Rest 与 Spread 兼容性一致,下列以 spread 为例:
3.Promise.prototype.finally()
Promise.prototype.finally() 方法返回一个 Promise,在 promise 执行结束时,无论结果是 fulfilled 或者是 rejected,在执行 then()和 catch()后,都会执行 finally 指定的回调函数。
1 | fetch("https://www.google.com") |
无论操作是否成功,当您需要在操作完成后进行一些清理时,finally()方法就派上用场了。这为指定执行完 promise 后,无论结果是 fulfilled 还是 rejected 都需要执行的代码提供了一种方式,避免同样的语句需要在 then()和 catch()中各写一次的情况。
Promise.prototype.finally()的支持情况:
4.新的正则表达式特性
ES9 为正则表达式添加了四个新特性,进一步提高了 JavaScript 的字符串处理能力。这些特点如下:
- s (dotAll) 标志
- 命名捕获组
- Lookbehind 后行断言
- Unicode 属性转义
(1)s(dotAll)flag
正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用 u 修饰符解决;另一个是行终止符,如换行符(\n)或回车符(\r),这个可以通过 ES9 的 s(dotAll)flag,在原正则表达式基础上添加 s 表示:
1 | console.log(/foo.bar/.test("foo\nbar")); // false |
那如何判断当前正则是否使用了 dotAll 模式呢?
1 | const re = /foo.bar/s; // Or, `const re = new RegExp('foo.bar', 's');`. |
(2)命名捕获组
在一些正则表达式模式中,使用数字进行匹配可能会令人混淆。例如,使用正则表达式/(\d{4})-(\d{2})-(\d{2})/来匹配日期。因为美式英语中的日期表示法和英式英语中的日期表示法不同,所以很难区分哪一组表示日期,哪一组表示月份:
1 | const re = /(\d{4})-(\d{2})-(\d{2})/; |
ES9 引入了命名捕获组,允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。
1 | const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; |
上面代码中,“命名捕获组”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?),然后就可以在 exec 方法返回结果的 groups 属性上引用该组名。
命名捕获组也可以使用在 replace()方法中,例如将日期转换为美国的 MM-DD-YYYY 格式:
1 | const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; |
(3)Lookbehind 后行断言
JavaScript 语言的正则表达式,只支持先行断言,不支持后行断言,先行断言我们可以简单理解为”先遇到一个条件,再判断后面是否满足”,如下面例子:
1 | let test = "hello world"; |
但有时我们想判断前面是 world 的 hello,这个代码是实现不了的。在 ES9 就支持这个后行断言了:
1 | let test = "world hello"; |
(?<…)是后行断言的符号,(?..)是先行断言的符号,然后结合 =(等于)、!(不等)、\1(捕获匹配)。
(4)Unicode 属性转义
ES2018 引入了一种新的类的写法\p{…}和\P{…},允许正则表达式匹配符合 Unicode 某种属性的所有字符。比如你可以使用\p{Number}来匹配所有的 Unicode 数字,例如,假设你想匹配的 Unicode 字符 ㉛ 字符串:
1 | const str = "㉛"; |
同样的,你可以使用\p{Alphabetic}来匹配所有的 Unicode 单词字符:
1 | const str = "ض"; |
同样有一个负向的 Unicode 属性转义模板 \P{…}
1 | console.log(/\P{Number}/u.test("㉛")); // → false |
除了字母和数字之外,Unicode 属性转义中还可以使用其他一些属性。
ES10 新特性
1.Array.prototype.flat()
多维数组是一种常见的数据格式,特别是在进行数据检索的时候。将多维数组打平是个常见的需求。通常我们能够实现,但是不够优雅。
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
1 | newArray = arr.flat(depth); // depth是指定要提取嵌套数组的结构深度,默认值为 1 |
接下来我们看两个例子:
1 | const numbers1 = [1, 2, [3, 4, [5, 6]]]; |
上面两个例子说明 flat 的参数没有设置,取默认值 1,也就是说只扁平化第一级;当 flat 的参数大于等于 2,返回值就是 [1, 2, 3, 4, 5, 6] 了。
Array.prototype.flat 的支持情况:
2.Array.prototype.flatMap()
有了 flat 方法,那自然而然就有 Array.prototype.flatMap 方法,flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为 1)。
1 | let arr = [1, 2, 3]; |
实际上 flatMap 是综合了 map 和 flat 的操作,所以它也只能打平一层。
Array.prototype.flatmap 的支持情况:
3.Object.fromEntries()
Object.fromEntries 这个新的 API 实现了与 Object.entries 相反的操作。这使得根据对象的 entries 很容易得到 object。
1 | const object = { x: 23, y: 24 }; |
ES2017 引入了 Object.entries, 这个方法可以将对象转换为数组,这样对象就可以使用数组原型中的众多内置方法,比如 map, filter、reduce,举个例子,我们想提取下列对象 obj 中所有 value 大于 21 的键值对,如何操作呢?
1 | // ES10之前 |
上例中得到了数组 arr,想再次转化为对象,就需要手动写一些代码来处理,但是有了 Object.fromEntries()就很容易实现
1 | // 用Object.fromEntries()来实现 |
Object.fromEntries()的支持情况:
4.String.trimStart 和 String.trimEnd
移除开头和结尾的空格,之前我们用正则表达式来实现,现在 ES10 新增了两个新特性,让这变得更简单!
trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。
1 | let str = " 01小仙女 "; |
trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。
1 | let str = " 01小仙女 "; |
String.trimStart 和 String.trimEnd 两者兼容性一致,下图以 trimStart 为例:
5.String.prototype.matchAll
如果一个正则表达式在字符串里面有多个匹配,现在一般使用 g 修饰符或 y 修饰符,在循环里面逐一取出。
1 | function collectGroup1(regExp, str) { |
值得注意的是,如果没有修饰符 /g, .exec() 只返回第一个匹配。现在通过 ES9 的 String.prototype.matchAll 方法,可以一次性取出所有匹配。
1 | function collectGroup1(regExp, str) { |
上面代码中,由于 string.matchAll(regex)返回的是遍历器,所以可以用 for…of 循环取出。
String.prototype.matchAll 的支持情况:
6.try…catch
在 ES10 中,try-catch 语句中的参数变为了一个可选项。以前我们写 catch 语句时,必须传递一个异常参数。这就意味着,即便我们在 catch 里面根本不需要用到这个异常参数也必须将其传递进去
1 | // ES10之前 |
这里 err 是必须的参数,在 ES10 可以省略这个参数:
1 | // ES10 |
try…catch 的支持情况:
7.BigInt
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于 2 的 1024 次方的数值,JavaScript 无法表示,会返回 Infinity。
1 | // 超过 53 个二进制位的数值,无法保持精度 |
现在 ES10 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
创建 BigInt 类型的值也非常简单,只需要在数字后面加上 n 即可。例如,123 变为 123n。也可以使用全局方法 BigInt(value) 转化,入参 value 为数字或数字字符串。
1 | const aNumber = 111; |
如果算上 BigInt,JavaScript 中原始类型就从 6 个变为了 7 个。
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (new in ECMAScript 2015)
- BigInt (new in ECMAScript 2019)
BigInt 的支持情况:
8.Symbol.prototype.description
我们知道,Symbol 的描述只被存储在内部的 [[Description]],没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:
1 | Symbol("desc").description; // "desc" |
Symbol.prototype.description 的支持情况:
9.Function.prototype.toString()
ES2019 中,Function.toString()发生了变化。之前执行这个方法时,得到的字符串是去空白符号的。而现在,得到的字符串呈现出原本源码的样子:
1 | function sum(a, b) { |
Function.prototype.toString()的支持情况: