ECMAScript
和 JavaScript
的关系
ECMAScript
是 JavaScript
的标准与规范,JavaScript
是 ECMAScript
标准的实现和扩展。
ES11
,即 ECMAScript 2020
。
ES12
,即 ECMAScript 2021
。
ES13
,即 ECMAScript 2022
。
ES11(ECMAScript 2020
)新特性
1、BigInt
在 JavaScript
中,数值类型 Number
是 64 位浮点数,所以计算精度和表示范围都有一定限制。
ES11
新增了 BigInt
数据类型,它可以表示任意大的整数。其语法如下:
BigInt(value);
其中 value
是创建对象的数值,可以是字符串或者整数。
在 JavaScript
中,Number
基本类型可以精确表示的最大整数是
2
53
\ 2^{53}\
253
因此之前会有这样的问题:
let max = Number.MAX_SAFE_INTEGER; // 最大安全整数 Math.pow(2, 53) - 1
let max1 = max + 1
let max2 = max + 2
max1 === max2 // true
有了 BigInt
之后,这个问题就不复存在了:
let max = BigInt(Number.MAX_SAFE_INTEGER);
let max1 = max + 1n
let max2 = max + 2n
max1 === max2 // false
1、如何判断变量是否为 BigInt
类型?
- 通过
typeof
操作符(返回字符串"bigint")
typeof 1n === 'bigint'; // true
typeof BigInt('1') === 'bigint'; // true
- 通过
Object.prototype.toString
方法(返回字符串"[object BigInt]"):
Object.prototype.toString.call(10n) === '[object BigInt]'; // true
2、BigInt
和 Number
的比较
10n === 10 // false
10n == 10 // true
// 比较
1n < 2; // true
2n > 1; // true
2 > 2; // false
2n > 2; // false
2n >= 2; // true
2、空值合并运算符(??)
在写代码时,如果某个属性不为 null
和 undefined
,那么就获取该属性,如果该属性为 null
或 undefined
,则取一个默认值:
// 三元运算符
const name = title ? title : 'default';
// || 运算符
const name = title || 'default';
但是 ||
的写法存在一定的缺陷,当 title 为 0 或 false 的时候也会走到 default 的逻辑。所以 ES11
引入了 ??
运算符。只有 ??
左边为 null
或 undefined
时才返回右边的值:
const title = false;
const name = title ?? 'default'; // name = false;
3、可选链操作符(?.)
在开发过程中,经常需要获取深层次属性,例如 res.data.userInfo.name
。但在获取 name
这个属性前需要一步步的判断前面的属性是否存在,否则并会报错:
const name = (res && res.data && res.data.userInfo && res.data.userInfo && res.data.userInfo.name) || 'default';
为了简化上述过程,ES11
引入了「链判断运算符」?.
,可选链操作符( ?.
)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。在引用为 null
或 undefined
的情况下不会引起错误,该表达式短路返回值是 undefined
。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined
。
const name = res?.data?.userInfo?.name || 'default';
可选链有以下三种形式:
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
4、Promise.allSettled
Promise.allSettled
的参数接受一个 Promise
的数组,返回一个新的 Promise
。唯一的不同在于,执行完之后不会失败,j即当 Promise.allSettled
全部处理完成后,可以拿到每个 Promise
的状态,而不管其是否处理成功。
下面使用 allSettled
实现的一段代码:
const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// [
// { status: 'fulfilled', value: 2 },
// { status: 'rejected', reason: -1 }
// ]
可以看到,Promise.allSettled
最后返回的是一个数组,记录传进来的参数中每个 Promise
的返回值,这就是和 all
方法不太一样的地方。
5、String.matchAll()
matchAll()
是新增的字符串方法,它返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。因为返回的是遍历器,所以通常使用 for...of
循环取出。
for (const match of 'abcabc'.matchAll(/a/g)) {
console.log(match)
}
//["a", index: 0, input: "abcabc", groups: undefined]
//["a", index: 3, input: "abcabc", groups: undefined]
需要注意,该方法的第一个参数是一个正则表达式对象,如果传的参数不是一个正则表达式对象,则会隐式地使用 new RegExp(obj)
将其转换为一个 RegExp
。另外,RegExp
必须是设置了全局模式(/g
)的形式,否则会抛出异常 TypeError
。
ES12(ECMAScript 2021
)新特性
1、String.replaceAll()
replaceAll()
方法会返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。
let string = 'hello world, hello ES12'
string.replace(/hello/g,'hi') // hi world, hi ES12
string.replaceAll('hello','hi') // hi world, hi ES12
注意的是,replaceAll
在使用正则表达式的时候,如果非全局匹配(/g
),会抛出异常:
let string = 'hello world, hello ES12'
string.replaceAll(/hello/,'hi')
// Uncaught TypeError: String.prototype.replaceAll called with a non-global
2、数字分隔符
数字分隔符可以在数字之间创建可视化分隔符,通过 _
下划线来分割数字,使数字更具可读性,可以放在数字内的任何地方:
const money = 1_000_000_000
//等价于
const money = 1000000000
该新特性同样支持在八进制数中使用:
const number = 0o123_456
//等价于
const number = 0o123456
3、Promise.any
Promise.any
是 ES12
新增的特性,它接收一个 Promise
可迭代对象(例如数组)。
- 只要其中的一个
promise
成功,就返回那个已经成功的promise
; - 如果可迭代对象中没有一个
promise
成功,就返回一个失败的promise
和AggregateError
类型的实例,它是Error
的一个子类,用于把单一的错误集合在一起。
const promises = [
Promise.reject('ERROR A'),
Promise.reject('ERROR B'),
Promise.resolve('result'),
]
Promise.any(promises).then((value) => {
console.log('value: ', value)
}).catch((err) => {
console.log('err: ', err)
})
// 输出结果:value: result
如果所有传入的 promises
都失败:
const promises = [
Promise.reject('ERROR A'),
Promise.reject('ERROR B'),
Promise.reject('ERROR C'),
]
Promise.any(promises).then((value) => {
console.log('value:', value)
}).catch((err) => {
console.log('err: ', err)
console.log(err.message)
console.log(err.name)
console.log(err.errors)
})
输出结果:
err: AggregateError: All promises were rejected
All promises were rejected
AggregateError
["ERROR A", "ERROR B", "ERROR C"]
4、逻辑赋值操作符
ES12
中新增了几个逻辑赋值操作符,可以用来简化一些表达式:
// 等同于 a = a || b
a ||= b;
// 等同于 c = c && d
c &&= d;
// 等同于 e = e ?? f
e ??= f;
ES13(ECMAScript 2022
)新特性
1、Object.hasOwn()
在 ES13
之前,可以使用 Object.prototype.hasOwnProperty()
来检查一个属性是否属于对象。
Object.hasOwn
特性是一种更简洁、更可靠的检查属性是否直接设置在对象上的方法:
const userInfo = {
name: 'admin'
};
console.log(Object.prototype.hasOwnProperty.call(userInfo, 'name'));
console.log(Object.hasOwn(userInfo, 'name'));
2、Array.at()
at()
是一个数组方法,用于通过给定索引来获取数组元素。当给定索引为正时,这种新方法与使用括号表示法访问具有相同的行为。当给出负整数索引时,就会从数组的最后一项开始检索。
const array = [0, 1, 2, 3, 4, 5];
console.log(array[array.length-1]); // 5
console.log(array.at(-1)); // 5
console.log(array[array.lenght-2]); // 4
console.log(array.at(-2)); // 4
除了数组,字符串也可以使用 at()
方法进行索引:
const str = "hello world";
console.log(str[str.length - 1]); // d
console.log(str.at(-1)); // d
3、error.cause
在 ES13
规范中,new Error()
中可以指定导致它的原因:
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
// ···
} catch (error) {
throw new Error(
`While processing ${filePath}`,
{cause: error}
);
}
});
}
4、Top-level Await
在 ES8
中,引入了 async
函数和 await
关键字,以简化 Promise
的使用,但是 await
关键字只能在 async
函数内部使用。如果在异步函数之外使用 await
就会报错:SyntaxError - SyntaxError: await is only valid in async function
。
顶层 await
允许在 async
函数外面使用 await
关键字。它允许模块充当大型异步函数,通过顶层 await
,这些 ECMAScript
模块可以等待资源加载。这样其他导入这些模块的模块在执行代码之前要等待资源加载完再去执行。
在 ES13
之前:
// user.js
let users;
export const fetchUsers = async () => {
const resp = await fetch('https://xxx/api/user');
users = resp.json();
}
fetchUsers();
export { users };
// mine.js
import { users } from './user.js';
console.log('users: ', users);
console.log('mine module');
我们还可以立即调用顶层 async
函数(IIAFE):
(async () => {
const resp = await fetch('https://xxx/api/user');
users = resp.json();
})();
export { users };
这样会有一个缺点,直接导入的 users
是 undefined
,需要在异步执行完成之后才能访问它:
// mine.js
import { users } from './user.js';
console.log('users:', users); // undefined
setTimeout(() => {
console.log('users:', users);
}, 100);
console.log('mine module');
而顶层 await
就可以解决问题:
// user.js
const resp = await fetch('https://xxx/api/user');
const users = resp.json();
export { users};
// mine.js
import {users} from './user.js';
console.log(users);
console.log('mine module');
顶级 await
在以下场景中将非常有用:
- 动态加载模块:
const lang = await import(`/i18n/${language}`);
- 资源初始化:
const connection = await getConnectParams();
- 依赖回退:
let citydata;
try {
citydata = await import('https://xxx.json');
} catch {
citydata = await import('https://xxx.xxx.json');
}