伴随 ECMAScript 标准持续不断地蓬勃演进,犹如为前端开发者打开了一扇又一扇充满惊喜的大门,源源不断地带来海量实用且强大的新特性。本文为大家精心分一些堪称 “效率神器” 的 ES 新特性,助力我们的代码实现质的飞跃,变得更加简洁明了、优雅动人,同时具备极高的执行效率。
1. 操作符
1.1 可选链操作符(?.)
可选链操作符(?.)能够帮助我们告别繁琐的空值检查,避免在访问对象属性时出现 TypeError
。如果对象链中的任何一个引用为 null
或 undefined
,则表达式停止计算并返回 undefined
。示例:
const user = {
name: 'Alice',
address: {
street: '123 Main St',
city: 'Anytown'
}
};
// 之前的写法
const oldCity = user && user.address && user.address.city;
// 使用可选链操作符
const city = user.address?.city;
console.log(city); // 输出: 'Anytown'
// 如果 address 为 null 或 undefined,不会抛出错误
const userWithoutAddress = { name: 'Bob' };
const city2 = userWithoutAddress.address?.city;
console.log(city2); // 输出: undefined
1.2 空值合并操作符(??)
空值合并操作符(??)可以在表达式中使用,当左侧的操作数为 null
或 undefined
时,返回右侧的操作数。示例:
const foo = null ?? 'default string';
console.log(foo); // 输出: 'default string'
const bar = 0 ?? 42;
console.log(bar); // 输出: 0
1.3 逻辑赋值操作符(||=、&&=、??=)
逻辑赋值操作符(||=、&&=、??=)可以帮助我们更简洁地编写条件语句。示例:
// 使用 ||= 操作符
let a = null;
a ||= 'default value'; // 等价于 a || (a = 'default value');
console.log(a); // 输出: 'default value'
// 使用 &&= 操作符
let b = true;
b &&= 'truthy value'; // 等价于 b && (b = 'truthy value');
console.log(b); // 输出: 'truthy value'
// 使用 ??= 操作符
let c = undefined;
c ??= 'default value'; // 等价于 c ?? (c = 'default value');
console.log(c); // 输出: 'default value'
1.4 数字分隔符(_)
数字分隔符可以让我们更清晰地阅读数字,提高代码可读性。示例:
const largeNumber = 1_000_000;
console.log(largeNumber); // 输出: 1000000
const bytes = 0xFF_FF_FF_FF;
1.5 指数运算符(**)
指数运算符(**)可以用来计算一个数的幂。示例:
const square = 2 ** 3;
console.log(square); // 输出: 8
2. 数组
2.1 Array.prototype.flat() 和 Array.prototype.flatMap()
flat()
方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。示例:
const nested = [1, [2, 3], [4, [5, 6]]];
const result1 = nested.flat(2); // [1, 2, 3, 4, 5, 6]
// 使用 flatMap
const nested2 = [[1], [2, 3], [4, [5, 6]]];
const result2 = nested2.flatMap(x => x[0]);
console.log(result2); // 输出: [1, 2, 4]
2.2 Array.prototype.at()
at()
方法接收一个整数值并返回该索引对应的元素,允许正数和负数。负数表示从数组末尾开始计数。示例:
const array = [5, 12, 8, 130, 44];
let index = 2;
console.log(array.at(index)); // 输出: 8
index = -2;
console.log(array.at(index)); // 输出: 130
2.3 Object.groupBy()
group()
方法将数组中的元素按照指定的分组函数进行分组,并返回一个对象,其中每个键对应一个分组。示例:
const array = [
{ type: 'fruit', name: 'apple' },
{ type: 'vegetable', name: 'carrot' },
{ type: 'fruit', name: 'banana' },
{ type: 'vegetable', name: 'onion' }
];
const grouped = Object.groupBy(array, (item) => item.type);
console.log(grouped);
// 输出:
// {
// fruit: [
// { type: 'fruit', name: 'apple' },
// { type: 'fruit', name: 'banana' }
// ],
// vegetable: [
// { type: 'vegetable', name: 'carrot' },
// { type: 'vegetable', name: 'onion' }
// ]
// }
2.4 其它数组操作方法
Array.prototype.toReversed()
: 返回一个新数组,其中元素的顺序与原数组相反。Array.prototype.toSorted()
: 返回一个新数组,其中元素按照指定的比较函数进行排序。Array.prototype.toSpliced()
: 返回一个新数组,其中删除了指定位置的元素,并在该位置插入了新元素。 这些操作都不会改变原数组,而是返回一个新数组。示例:
const array = [1, 2, 3, 4, 5];
// 使用 toReversed()
const reversedArray = array.toReversed();
console.log(reversedArray); // 输出: [5, 4, 3, 2, 1]
// 使用 toSorted()
const sortedArray = array.toSorted((a, b) => b - a);
console.log(sortedArray); // 输出: [5, 4, 3, 2, 1]
// 使用 toSpliced()
const splicedArray = array.toSpliced(1, 2, 10, 11);
console.log(splicedArray); // 输出: [1, 10, 11, 5]
3. 对象
3.1 私有类字段(Private Class Fields)
私有类字段允许我们在类中定义私有属性和方法,这些属性和方法只能在类的内部访问,不能在类的实例上访问。示例:
class Person {
#name;
constructor(name) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}`);
}
}
const person = new Person('Alice');
person.greet(); // 输出: Hello, my name is Alice
console.log(person.#name); // 报错: Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class
Chrome 控制台中运行的代码可以访问类的私有属性。这是 JavaScript 语法限制对开发者工具的一种放宽。
3.2 对象字面量增强
提供了更简洁的对象属性和方法定义方式。
const name = 'Tom';
const age = 18;
const person = {
name,
age,
sayHi() {
console.log('Hi!');
}
};
3.3 类静态初始化块
类静态初始化块允许你在类的定义中包含一个或多个静态初始化块。这些块在类被加载时执行,并且可以用来初始化类的静态属性或执行其他静态操作。
class MyClass {
static staticProperty;
static {
// 这是一个静态初始化块
this.staticProperty = 'Hello, Static Property!';
console.log('Static initialization block executed');
}
static staticMethod() {
console.log(this.staticProperty);
}
}
MyClass.staticMethod(); // 输出: "Hello, Static Property!"
3.4 Object.hasOwn()
Object.hasOwn()
提供了一种更安全、更可靠的方式来检查对象是否拥有指定的属性,可以替代 Object.prototype.hasOwnProperty()
方法,因为它不会受到对象原型链上属性的影响。
const obj = { name: 'Alice', age: 30 };
console.log(Object.hasOwn(obj, 'name')); // 输出: true
console.log(Object.hasOwn(obj, 'gender')); // 输出: false
// 与 Object.prototype.hasOwnProperty() 方法的比较
console.log(obj.hasOwnProperty('name')); // 输出: true
console.log(obj.hasOwnProperty('gender')); // 输出: false
3.5 装饰器
装饰器(Decorators)是一种特殊的声明,它可以被附加到类声明、方法、访问器、属性或参数上。装饰器使用@expression的形式,其中expression必须是一个函数,它会在运行时被调用,以修改类的行为。
function logged(target, context) {
return class extends target {
exec(...args) {
console.log('Starting execution...');
const result = super.exec(...args);
console.log('Finished execution.');
return result;
}
};
}
@logged
class Example {
exec() {
// ...
}
}
目前装饰器在浏览器中的支持还处于实验阶段,需要使用 Babel 或 TypeScript 等工具进行转换。
4. 异步操作Promise
4.1 Promise.allSettled()
Promise.allSettled()
方法允许同时处理多个 Promise,并在所有 Promise 都已解决(无论是成功还是失败)后返回一个结果数组。与 Promise.all() 不同,Promise.allSettled() 不会因为其中一个 Promise 失败而立即拒绝,而是会等待所有 Promise 都完成后再返回结果。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 100, 'foo'),
);
const promises = [promise1, promise2];
Promise.allSettled(promises).then((results) =>
results.forEach((result) => console.log(result.status)),
);
// Expected output:
// "fulfilled"
// "rejected"
4.2 Promise.any()
Promise.any() 接受一个 Promise 可迭代对象(例如数组)作为参数,并返回一个新的 Promise。这个新的 Promise 会在可迭代对象中的任何一个 Promise 成功时立即解析,其解析值为第一个成功的 Promise 的结果。
const promises = [
Promise.reject(1),
Promise.reject(2),
Promise.resolve(3)
];
Promise.any(promises).then((result) => {
console.log(result); // 输出: 3
}).catch((error) => {
console.log(error);
});
4.3 Promise.withResolvers()
Promise.withResolvers()
同时返回 Promise 本身以及它的 resolve 和 reject 函数, 提供了一种更优雅的 Promise 控制方式。
const { promise, resolve, reject } = Promise.withResolvers();
// 模拟异步操作
setTimeout(() => {
const randomNumber = Math.random();
if (randomNumber > 0.5) {
resolve(randomNumber);
} else {
reject(new Error('Random number is too low'));
}
}, 1000);
// 使用 Promise
promise
.then(result => console.log('Resolved:', result))
.catch(error => console.error('Rejected:', error));
// Resolved: 0.7118004612730797
// 或
// Rejected: Error: Random number is too low
4.4 顶层await
在模块顶层使用await,无需包含在async
中,目的是借用await解决模块异步加载的问题。
// import() 方法加载
const strings = await import(`/i18n/${navigator.language}`);
// 数据库操作
const connection = await dbConnector();
// 依赖回滚
let jQuery;
try {
jQuery = await import('https://cdn-a.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.com/jQuery');
}
5. 其它
5.1 动态导入(Dynamic Import)
动态导入(Dynamic Import)允许在运行时动态地加载模块。这对于需要按需加载模块的情况非常有用,有利于优化页面性能。例如在单页应用程序(SPA)中,你可能只需要在用户导航到特定页面时加载该页面所需的模块。
// 动态导入一个模块
import('./module.js')
.then(module => {
// 使用模块的导出
module.exportedFunction();
})
.catch(error => {
// 处理导入错误
console.error('Error importing module:', error);
});
5.2 BigInt
BigInt
是一种新的数据类型,它提供了一种方法来表示大于 2^53 - 1 的整数,避免了 JavaScript 中整数的精度限制。
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n
5.3 globalThis
globalThis
提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身),无论当前环境是浏览器、Node.js 还是其他环境。
if (typeof globalThis.setTimeout !== "function") {
// 此环境中没有 setTimeout 方法!
}
5.4 String.prototype.matchAll()
String.prototype.matchAll()
具有更强大的字符串匹配能力,它返回一个迭代器,其中包含所有匹配正则表达式的结果,包括捕获组。
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const matches = str.matchAll(regexp);
for (const match of matches) {
console.log(match);
}
// 输出:
// ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]
5.5 String.prototype.replaceAll()
String.prototype.replaceAll()
方法用于在字符串中替换所有匹配正则表达式的子串。它返回一个新的字符串,其中所有匹配的子串都被替换为指定的字符串。
const str = 'Hello, world!';
const newStr = str.replaceAll('l', 'x');
console.log(newStr); // 输出: "Hexxo, worxd!"
5.6 WeakRef 和 FinalizationRegistry
WeakRef
和 FinalizationRegistry
可以帮助我们更好地管理内存和垃圾回收,提高前端应用的性能和稳定性。
// WeakRef
const obj = { name: 'Alice' };
const weakRef = new WeakRef(obj);
// 检查对象是否存在
if (weakRef.deref()) {
console.log('Object still exists');
} else {
console.log('Object has been garbage collected');
}
// FinalizationRegistry
const registry = new FinalizationRegistry((value) => {
console.log(`Object ${value} has been garbage collected`);
});
const obj2 = { name: 'Bob' };
registry.register(obj2, 'Bob');
// 移除对象的注册
registry.unregister(obj2);
5.7 错误原因(Error Cause)
更好的追踪错误的原因。
try {
// 一些可能会抛出错误的代码
throw new Error('Something went wrong', { cause: new Error('Original error') });
} catch (error) {
console.error('Caught error:', error.message);
console.error('Cause:', error.cause.message);
}
5.8 正则表达式命名捕获组
Regex.exec()方法返回的结果中,捕获组的名称也被包含在结果数组中。
5.9 正则表达式命名捕获组(RegExp.prototype.exec)
RegExp.prototype.exec()
在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const str = 'Today is 2023-10-01';
const result = regex.exec(str);
console.log(result);
// 输出:
// [
// '2023-10-01',
// '2023',
// '10',
// '01',
// index: 9,
// input: 'Today is 2023-10-01',
// groups: {year: '2023', month: '10', day: '01'}
// ]
总结
如有错误,欢迎指正!也欢迎大家进行补充~