现代 JavaScript 特性
随着 ECMAScript 标准的不断发展,JavaScript 语言持续引入新的特性和改进。这些现代 JavaScript 特性使开发者能够编写更简洁、更高效、更易维护的代码。
ES2016 (ES7) 特性
Array.prototype.includes()
includes() 方法用来判断数组是否包含某个元素,返回布尔值。
javascript
let fruits = ["苹果", "香蕉", "橙子"];
console.log(fruits.includes("香蕉")); // true
console.log(fruits.includes("葡萄")); // false
// 检查第二个参数指定的索引位置之后是否包含元素
console.log(fruits.includes("苹果", 1)); // false
// 对于 NaN 的处理
let numbers = [1, 2, NaN];
console.log(numbers.includes(NaN)); // true指数运算符 (**)
指数运算符提供了更简洁的幂运算语法。
javascript
console.log(2 ** 3); // 8
console.log(3 ** 2); // 9
// 与 Math.pow() 等价
console.log(2 ** 3 === Math.pow(2, 3)); // true
// 支持赋值运算符
let num = 2;
num **= 3;
console.log(num); // 8ES2017 (ES8) 特性
async/await
async/await 提供了更优雅的异步编程方式。
javascript
// 使用 Promise
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.error(error);
});
}
// 使用 async/await
async function fetchDataAsync() {
try {
let response = await fetch('/api/data');
let data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error(error);
}
}
// 并行执行多个异步操作
async function fetchMultipleData() {
try {
let [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { users, posts, comments };
} catch (error) {
console.error('获取数据失败:', error);
}
}Object.values() 和 Object.entries()
这两个方法提供了更方便的对象遍历方式。
javascript
let user = {
name: "张三",
age: 25,
job: "开发者"
};
// Object.values() 返回对象值的数组
console.log(Object.values(user));
// ["张三", 25, "开发者"]
// Object.entries() 返回对象键值对的数组
console.log(Object.entries(user));
// [["name", "张三"], ["age", 25], ["job", "开发者"]]
// 实际应用示例
// 将对象转换为查询字符串
function objectToQueryString(obj) {
return Object.entries(obj)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
}
let params = { name: "张三", age: 25, city: "北京" };
console.log(objectToQueryString(params));
// "name=%E5%BC%A0%E4%B8%89&age=25&city=%E5%8C%97%E4%BA%AC"String padding
字符串填充方法用于在字符串的开始或结尾添加指定字符。
javascript
// padStart() 在字符串开头填充
console.log('5'.padStart(2, '0')); // "05"
console.log('99'.padStart(4, '0')); // "0099"
console.log('hello'.padStart(10)); // " hello"
// padEnd() 在字符串结尾填充
console.log('5'.padEnd(2, '0')); // "50"
console.log('99'.padEnd(4, '0')); // "9900"
console.log('hello'.padEnd(10)); // "hello "
// 实际应用:格式化时间显示
function formatTime(hours, minutes, seconds) {
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
console.log(formatTime(9, 5, 7)); // "09:05:07"Object.getOwnPropertyDescriptors()
获取对象所有自有属性的描述符。
javascript
let obj = {
name: "张三",
get age() {
return 25;
}
};
let descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
// {
// name: {value: "张三", writable: true, enumerable: true, configurable: true},
// age: {get: ƒ, set: undefined, enumerable: true, configurable: true}
// }
// 可用于正确复制对象(包括 getter 和 setter)
let copied = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);ES2018 (ES9) 特性
异步迭代
异步迭代允许我们使用 for-await-of 循环遍历异步可迭代对象。
javascript
// 创建异步可迭代对象
async function* asyncNumbers() {
let i = 0;
while (i < 3) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i++;
}
}
// 使用 for-await-of 遍历
async function printAsyncNumbers() {
for await (let num of asyncNumbers()) {
console.log(num);
}
}
// 实际应用:逐行读取文件
async function* readLines(file) {
let lines = file.split('\n');
for (let line of lines) {
yield line;
}
}
async function processFile() {
let fileContent = "第一行\n第二行\n第三行";
for await (let line of readLines(fileContent)) {
console.log(line);
}
}Promise.finally()
finally() 方法用于指定无论 Promise 对象最后状态如何都会执行的回调函数。
javascript
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.catch(error => {
console.error('请求失败:', error);
throw error;
})
.finally(() => {
console.log('请求完成(无论成功还是失败)');
// 可以在这里进行清理工作,如隐藏加载指示器
hideLoadingIndicator();
});
}
function hideLoadingIndicator() {
// 隐藏加载指示器的代码
console.log('隐藏加载指示器');
}对象展开运算符
对象展开运算符允许我们展开对象属性。
javascript
let obj1 = { a: 1, b: 2 };
let obj2 = { c: 3, d: 4 };
// 合并对象
let merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
// 复制对象
let copy = { ...obj1 };
console.log(copy); // { a: 1, b: 2 }
// 覆盖属性
let updated = { ...obj1, b: 20 };
console.log(updated); // { a: 1, b: 20 }正则表达式改进
命名捕获组
javascript
// 传统捕获组
let regex1 = /(\d{4})-(\d{2})-(\d{2})/;
let match1 = regex1.exec('2023-10-25');
console.log(match1[1]); // "2023"
console.log(match1[2]); // "10"
console.log(match1[3]); // "25"
// 命名捕获组
let regex2 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let match2 = regex2.exec('2023-10-25');
console.log(match2.groups.year); // "2023"
console.log(match2.groups.month); // "10"
console.log(match2.groups.day); // "25"
// 在替换字符串中使用命名捕获组
let date = '2023-10-25';
let formatted = date.replace(regex2, '$<month>/$<day>/$<year>');
console.log(formatted); // "10/25/2023"反向断言
javascript
// 正向先行断言
let text = "价格是 $100";
let regex1 = /\$(?=\d+)/; // 匹配后面跟着数字的 $
console.log(text.match(regex1)); // ["$"]
// 负向先行断言
let regex2 = /\$(?!\d+)/; // 匹配后面不跟数字的 $
console.log(text.match(regex2)); // null
// 正向后行断言
let regex3 = /(?<=\$)\d+/; // 匹配前面是 $ 的数字
console.log(text.match(regex3)); // ["100"]
// 负向后行断言
let regex4 = /(?<!\$)\d+/; // 匹配前面不是 $ 的数字
let text2 = "价格是 100 美元";
console.log(text2.match(regex4)); // ["100"]dotAll 模式
javascript
// 传统模式下,点号不匹配换行符
let regex1 = /foo.bar/;
console.log(regex1.test('foo\nbar')); // false
// 使用 s 标志启用 dotAll 模式
let regex2 = /foo.bar/s;
console.log(regex2.test('foo\nbar')); // trueES2019 (ES10) 特性
Array.prototype.flat() 和 flatMap()
javascript
// flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
let arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // [1, 2, 3, 4]
let arr2 = [1, 2, [3, [4, 5]]];
console.log(arr2.flat()); // [1, 2, 3, [4, 5]]
console.log(arr2.flat(2)); // [1, 2, 3, 4, 5]
// flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组
let arr = [1, 2, 3];
console.log(arr.flatMap(x => [x, x * 2]));
// [1, 2, 2, 4, 3, 6]Object.fromEntries()
将键值对列表转换为对象。
javascript
let entries = [['name', '张三'], ['age', 25]];
let obj = Object.fromEntries(entries);
console.log(obj); // { name: "张三", age: 25 }
// 实际应用:从 URLSearchParams 创建对象
let params = new URLSearchParams('name=张三&age=25');
let paramObj = Object.fromEntries(params);
console.log(paramObj); // { name: "张三", age: "25" }String.prototype.trimStart() 和 trimEnd()
javascript
let str = ' hello world ';
console.log(str.trimStart()); // "hello world "
console.log(str.trimEnd()); // " hello world"
console.log(str.trim()); // "hello world"可选的 catch 绑定
javascript
// 传统写法
try {
someRiskyOperation();
} catch (error) {
// 即使不使用 error,也必须声明
console.log('操作失败');
}
// ES2019 允许省略 catch 参数
try {
someRiskyOperation();
} catch {
console.log('操作失败');
}ES2020 (ES11) 特性
可选链操作符 (?.)
javascript
let user = {
name: "张三",
address: {
street: "长安街",
city: "北京"
}
};
// 传统写法需要多次检查
if (user && user.address && user.address.city) {
console.log(user.address.city);
}
// 可选链操作符
console.log(user?.address?.city); // "北京"
console.log(user?.address?.zipcode); // undefined
console.log(user?.contact?.email); // undefined
// 可选链与函数调用
let obj = {
method: function() {
return "hello";
}
};
console.log(obj.method?.()); // "hello"
console.log(obj.nonexistent?.()); // undefined空值合并操作符 (??)
javascript
// 与 || 的区别
let value1 = "" || "默认值";
console.log(value1); // "默认值"
let value2 = "" ?? "默认值";
console.log(value2); // ""
let value3 = null ?? "默认值";
console.log(value3); // "默认值"
let value4 = undefined ?? "默认值";
console.log(value4); // "默认值"
// 实际应用
function configure(settings) {
return {
theme: settings.theme ?? "light",
lang: settings.lang ?? "zh-CN",
notifications: settings.notifications ?? true
};
}Promise.allSettled()
等待所有 Promise 完成(无论成功还是失败)。
javascript
let promises = [
Promise.resolve("成功1"),
Promise.reject("失败1"),
Promise.resolve("成功2")
];
Promise.allSettled(promises).then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: '成功1' },
// { status: 'rejected', reason: '失败1' },
// { status: 'fulfilled', value: '成功2' }
// ]
// 统计成功和失败的数量
let fulfilled = results.filter(r => r.status === 'fulfilled').length;
let rejected = results.filter(r => r.status === 'rejected').length;
console.log(`成功: ${fulfilled}, 失败: ${rejected}`);
});globalThis
提供了一个标准的方式来访问全局对象。
javascript
// 在浏览器中
console.log(globalThis === window); // true
// 在 Node.js 中
console.log(globalThis === global); // true
// 在 Web Workers 中
console.log(globalThis === self); // true
// 统一访问全局对象的方式
globalThis.myGlobalVar = "全局变量";现代 JavaScript 特性持续为开发者提供更强大、更简洁的编程工具。掌握这些新特性有助于编写更高质量的代码,提高开发效率。