ES10新特性(2019)
- 更加友好的 JSON.stringify
- 新增了Array的
flat()方法和flatMap()方法 - 新增了String的
trimStart()方法和trimEnd()方法 Object.fromEntries()Symbol.prototype.descriptionString.prototype.matchAllFunction.prototype.toString()现在返回精确字符,包括空格和注释- 简化
try {} catch {},修改catch绑定 - 新的基本数据类型
BigInt - globalThis
- import()
1.更加友好的 JSON.stringify
如果输入 Unicode 格式但是超出范围的字符,在原先JSON.stringify返回格式错误的Unicode字符串。现在实现了一个改变JSON.stringify的第3阶段提案,因此它为其输出转义序列,使其成为有效Unicode(并以UTF-8表示)
2.新增了Array的flat()方法和flatMap()方法
flat()和flatMap()本质上就是是归纳(reduce) 与 合并(concat)的操作。
Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
flat()方法最基本的作用就是数组降维
var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]
var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]
//使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity);
// [1, 2, 3, 4, 5, 6]
- 其次,还可以利用
flat()方法的特性来去除数组的空项
var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]
Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。 这里我们拿map方法与flatMap方法做一个比较。
var arr1 = [1, 2, 3, 4];
arr1.map(x => [x * 2]);
// [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]
// 只会将 flatMap 中的函数返回的数组 “压平” 一层
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]
4.新增了String的trimStart()方法和trimEnd()方法
新增的这两个方法很好理解,分别去除字符串首尾空白字符,这里就不用例子说声明了。
5.Object.fromEntries()
Object.entries()方法的作用是返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。
而Object.fromEntries() 则是 Object.entries() 的反转。
Object.fromEntries() 函数传入一个键值对的列表,并返回一个带有这些键值对的新对象。它生成一个具有两个元素的类似数组的对象,第一个元素是将用作属性键的值,第二个元素是与该属性键关联的值。
- 通过 Object.fromEntries, 可以将 Map 转化为 Object:
const map = new Map([ ['foo', 'bar'], ['baz', 42] ]);
const obj = Object.fromEntries(map);
console.log(obj); // { foo: "bar", baz: 42 }
- 通过 Object.fromEntries, 可以将 Array 转化为 Object:
const arr = [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ];
const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }
6.Symbol.prototype.description
通过工厂函数Symbol()创建符号时,您可以选择通过参数提供字符串作为描述:
const sym = Symbol('The description');
以前,访问描述的唯一方法是将符号转换为字符串:
assert.equal(String(sym), 'Symbol(The description)'); // true
现在引入了getter Symbol.prototype.description以直接访问描述:
assert.equal(sym.description, 'The description');
7.String.prototype.matchAll
matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器。 在 matchAll 出现之前,通过在循环中调用regexp.exec来获取所有匹配项信息(regexp需使用/g标志:
const regexp = RegExp('foo*','g');
const str = 'table football, foosball';
while ((matches = regexp.exec(str)) !== null) {
console.log(`Found ${matches[0]}. Next starts at ${regexp.lastIndex}.`);
// expected output: "Found foo. Next starts at 9."
// expected output: "Found foo. Next starts at 19."
}
如果使用matchAll ,就可以不必使用while循环加exec方式(且正则表达式需使用/g标志)。使用matchAll 会得到一个迭代器的返回值,配合 for...of, array spread, or Array.from() 可以更方便实现功能:
const regexp = RegExp('foo*','g');
const str = 'table football, foosball';
let matches = str.matchAll(regexp);
for (const match of matches) {
console.log(match);
}
// Array [ "foo" ]
// Array [ "foo" ]
// matches iterator is exhausted after the for..of iteration
// Call matchAll again to create a new iterator
matches = str.matchAll(regexp);
Array.from(matches, m => m[0]);
// Array [ "foo", "foo" ]
matchAll可以更好的用于分组
var regexp = /t(e)(st(\d?))/g;
var str = 'test1test2';
str.match(regexp);
// Array ['test1', 'test2']
let array = [...str.matchAll(regexp)];
array[0];
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
array[1];
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
8.Function.prototype.toString()现在返回精确字符,包括空格和注释
function /* comment */ foo /* another comment */() {}
// 之前不会打印注释部分
console.log(foo.toString()); // function foo(){}
// ES2019 会把注释一同打印
console.log(foo.toString()); // function /* comment */ foo /* another comment */ (){}
// 箭头函数
const bar /* comment */ = /* another comment */ () => {};
console.log(bar.toString()); // () => {}
9.修改 catch 绑定
在 ES10 之前,我们必须通过语法为 catch 子句绑定异常变量,无论是否有必要。很多时候 catch 块是多余的。 ES10 提案使我们能够简单的把变量省略掉。
不算大的改动。
之前是
try {} catch(e) {}
现在是
try {} catch {}
10.新的基本数据类型BigInt
现在的基本数据类型(值类型)不止5种(ES6之后是六种)了哦!加上BigInt一共有七种基本数据类型,分别是: String、Number、Boolean、Null、Undefined、Symbol、BigInt
ES11 (ECMAScript 2020 新特性)
- Optional Chaining 可选链式调用
- Nullish Coalescing 空值合并
- Dynamic Import 动态引入
- globalThis 全局对象
Optional Chaining 可选链式调用
大部分开发者都遇到过这个问题:
TypeError: Cannot read property ‘x’ of undefined
这个错误表示我们正在访问一个不属于对象的属性。
访问对象的属性
const flower = {
colors: {
red: true
}
}
console.log(flower.colors.red) // 正常运行
console.log(flower.species.lily) // 抛出错误:TypeError: Cannot read property 'lily' of undefined
在这种情况下,JavaScript 引擎会像这样抛出错误。但是某些情况下值是否存在并不重要,因为我们知道它会存在。于是,可选链式调用就派上用场了! 我们可以使用由一个问号和一个点组成的可选链式操作符,去表示不应该引发错误。如果没有值,应该返回 undefined。
console.log(flower.species?.lily) // 输出 undefined
当访问数组或调用函数时,也可以使用可选链式调用。
let flowers = ['lily', 'daisy', 'rose']
console.log(flowers[1]) // 输出:daisy
flowers = null
console.log(flowers[1]) // 抛出错误:TypeError: Cannot read property '1' of null
console.log(flowers?.[1]) // 输出:undefined
Nullish Coalescing 空值合并
目前,要为变量提供回退值,逻辑操作符 || 还是必须的。它适用于很多情况,但不能应用在一些特殊的场景。例如,初始值是布尔值或数字的情况。举例说明,我们要把数字赋值给一个变量,当变量的初始值不是数字时,就默认其为 7 :
let number = 1
let myNumber = number || 7
变量 myNumber 等于 1,因为左边的(number)是一个 真 值 1。但是,当变量 number 不是 1 而是 0 呢?
let number = 0
let myNumber = number || 7
0 是假值,所以即使 0 是数字。变量 myNumber 将会被赋值为右边的 7。但结果并不是我们想要的。幸好,由两个问号组成:??的合并操作符就可以检查变量 number 是否是一个数字,而不用写额外的代码了。
let number = 0
let myNumber = number ?? 7
操作符右边的值仅在左边的值等于 null 或 undefined 时有效,因此,例子中的变量 myNumber 现在的值等于 0 了。
Dynamic Import 动态引入
你也许在 webpack 的模块绑定中已经使用过动态引入。但对于该特性的原生支持已经到来:
// Alert.js
export default {
show() {
// 代码
}
}
// 使用 Alert.js 的文件
import('/components/Alert.js')
.then(Alert => {
Alert.show()
})
globalThis 全局对象
JavaScript 可以在不同环境中运行,比如浏览器或者 Node.js。浏览器中可用的全局对象是变量 window,但在 Node.js 中是一个叫做 global 的对象。为了在不同环境中都使用统一的全局对象,引入了 globalThis 。
// 浏览器
window == globalThis // true
// node.js
global == globalThis // true
ES12 (ECMAScript 2021 新特性)
- String.prototype.replaceAll()
- Promise.any
- 逻辑运算符和赋值表达式
- 数值分隔符
- WeakRef and Finalizers
String.prototype.replaceAll()
const newString = oldString.replaceAll(pattern, replacement);
该方法返回一个新的字符串,所有 pattern会被传递给它的replacement替换。 其中pattern参数可以是字符串或正则表达式,replacement参数可以是字符串或针对每次匹配执行的函数。
replaceAll方法是String.replace方法的续集,String.replace仅替换pattern的第一次找到的位置。
const str = "Linda is a self-taught developer.Linda will rule the world";
let newStr = str.replace("Linda","Micheal")
//output: Micheal is a self-taught developer.Linda will rule the world
let newStr = str.replaceAll("Linda","Micheal")
//output: Micheal is a self-taught developer.Micheal will rule the world
以往如果需要替换所有匹配项的话,就要写成正则表达式,才能进行完全替换。
const str = "hello world, hello code";
const newStr = str.replace(/hello/g, "hi");
console.log(newStr); // "hi world, hi code"
现在有了String.prototype.replaceAll() 即便是输入字符串也能完全替换匹配项。
const str = "hello world, hello code";
const newStr = str.replaceAll("hello", "hi");
console.log(newStr); // "hi world, hi code"
Promise.any
在 ES6 中引入了 Promise.race() 和 Promise.all() 方法,ES2020 加入了 Promise.allSettled()。 ES2021 又增加了一个使 Promise 处理更加容易的方法:Promise.any()
Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise (如示例 1 所示)。如果可迭代对象中没有一个 promise 成功(即所有的 promise 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例(如示例 2 所示),它是 Error的一个子类,用于把单一的错误集合在一起。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。
// 示例1
Promise.any([
new Promise((resolve, reject) => setTimeout(reject, 200, 'Third')),
new Promise((resolve, reject) => setTimeout(resolve, 1000, 'Second')),
new Promise((resolve, reject) => setTimeout(resolve, 2000, 'First')),
])
.then(value => console.log(`Result1: ${value}`))
.catch (err => console.log(err))
Promise.race([
new Promise((resolve, reject) => setTimeout(reject, 200, 'Third')),
new Promise((resolve, reject) => setTimeout(resolve, 1000, 'Second')),
new Promise((resolve, reject) => setTimeout(resolve, 2000, 'First')),
])
.then(value => console.log(`Result2: ${value}`))
.catch (err => console.log(err))
/**** Output ****/
// Third
// Result1: Second
上面代码中,Promise.any()方法的参数数组包含三个 Promise 操作。其中只要有一个变成fulfilled,Promise.any()返回的 Promise 对象就变成fulfilled,而Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
如果没有 fulfilled (成功的) promise,Promise.any() 返回 AggregateError 错误。
// 示例 2
const pErr1 = new Promise((resolve, reject) => {
reject('总是失败1');
});
const pErr2 = new Promise((resolve, reject) => {
reject('总是失败2');
});
Promise.any([pErr1,pErr2]).catch((err) => {
console.log(err);
})
/**** Output ****/
// "AggregateError: All promises were rejected"
逻辑运算符和赋值表达式(&&=,||=,??=)
在 JavaScript 中有许多赋值运算符和逻辑运算符,如以下基本示例:
// Assignment Operator Example
let num = 5
num+=10
console.log(num) // 15
// Logical Operator Example
let num1 = 6
let num2 = 3
console.log(num1 === 6 && num2 === 2) // false
console.log(num1 === 6 || num2 === 2) // true
新的提案让我们将能把逻辑运算符和赋值运算符结合起来
1、带有 && 运算符的逻辑赋值运算符
var x = 1;
var y = 2;
x &&= y;
console.log(x); // 2
第3行的操作等价为:x && (x = y) 或者等价于
if(x) {
x = y
}
由于x是真实值,因此赋值了的值y,即2。简而言之,运算符&&=仅当 LHS 值为真时,才将 RHS 变量值赋给 LHS 变量。
2、带有||的运算符逻辑赋值运算符
仅当 LHS 值为假时,才将 RHS 变量值赋给 LHS 变量。
// Logical Assignment Operator with || operator
let num1
let num2 = 10
num1 ||= num2
console.log(num1) // 10
// Line 4 can also be written as following ways
// 1. num1 || (num1 = num2)
// 2. if (!num1) num1 = num2
3、带有?? 运算符的逻辑赋值运算符
ES2020 引入了空值合并运算符,其也可以与赋值运算符结合使用。仅当 LHS 为 undefined 或仅为 null 时,才将 RHS 变量值赋给 LHS 变量。
// Logical Assignment Operator with ?? operator
let num1
let num2 = 10
num1 ??= num2
console.log(num1) // 10
num1 = false
num1 ??= num2
console.log(num1) // false
// Line 4 can also be written as following ways
// num1 ?? (num1 = num2)
数值分隔符
我们将通过使用_(下划线)字符在数字组之间提供分隔,使读取数值更加容易,提高可读性。
let x = 100_000; // 100000
数值分隔符也适用于BigInt数字。
const trillion = 1000_000_000_000n;
console.log(trillion.toString()); // "1000000000000"
分隔符仅出于可读性目的。因此,它可以放置在数字中的任何位置。
const amount = 178_00;
console.log(amount.toString()); // "17800"
WeakRef and Finalizers
此功能包含两个高级对象 WeakRef 和 FinalizationRegistry。根据使用情况,这些接口可以单独使用,也可以一起使用。**官方建议不要轻易使用 WeakRef 和 finalizer。**其中一个原因是它们可能不可预测,另一个是它们并没有真正帮 gc 完成工作,实际上可能会垃圾回收的工作更加困难。
在 JavaScript 中,当你创建了一个创建对象的引用时,这个引用可以防止对象被垃圾收集,也就是说 JavaScript 无法删除对象并释放其内存。只要对该对象的引用一直存在,就可以使这个对象永远存在。
ES2021 了新的类 WeakRefs。允许创建对象的弱引用。这样就能够在跟踪现有对象时不会阻止对其进行垃圾回收。对于缓存和对象映射非常有用。
必须用 new关键字创建新的 WeakRef ,并把某些对象作为参数放入括号中。当你想读取引用(被引用的对象)时,可以通过在弱引用上调用 deref() 来实现。
const myWeakRef = new WeakRef({
name: '星野',
year: '25'
})
myWeakRef.deref()
// => { name: '星野', year: '25' }
myWeakRef.deref().name
// => '星野'
与 WeakRef 紧密相连的还有另一个功能,名为 finalizers 或 FinalizationRegistry。这个功能允许你注册一个回调函数,这个回调函数将会在对象被垃圾回收时调用。
// 创建 FinalizationRegistry:
const reg = new FinalizationRegistry((val) => {
console.log(val)
})
(() => {
// 创建新对象:
const obj = {}
//为 “obj” 对象注册 finalizer:
//第一个参数:要为其注册 finalizer 的对象。
//第二个参数:上面定义的回调函数的值。
reg.register(obj, 'obj has been garbage-collected.')
})()
// 当 "obj" 被垃圾回收时输出:
// 'obj has been garbage-collected.'