ECMAScript ES6-ES15 完全指南:现代 JavaScript 语法进化与学习路线
ECMAScript ES6-ES15 完全指南:现代 JavaScript 语法进化与学习路线
ECMAScript(简称 ES)是 JavaScript 语言的标准规范,由 ECMA 国际组织发布。自 ES6(2015年)起,ECMAScript 采用年度发布周期,每个版本都会带来新的语法特性、性能提升和开发体验改进。本文将详细介绍从 ES6 到 ES15 的所有重要特性,为你提供完整的学习路线图。
📋 ECMAScript 版本时间线
| 版本 | 发布年份 | 主要特性 | 重要性 |
|---|---|---|---|
| ES6/ES2015 | 2015 | let/const、箭头函数、Promise、类、模块化 | ⭐⭐⭐⭐⭐ 革命性版本 |
| ES7/ES2016 | 2016 | Array.includes()、指数运算符 | ⭐⭐⭐ 实用增强 |
| ES8/ES2017 | 2017 | async/await、Object.values() | ⭐⭐⭐⭐ 异步编程革新 |
| ES9/ES2018 | 2018 | 对象扩展运算符、异步迭代 | ⭐⭐⭐ 语法完善 |
| ES10/ES2019 | 2019 | Array.flat()、Object.fromEntries() | ⭐⭐⭐ 实用工具 |
| ES11/ES2020 | 2020 | 可选链、空值合并、动态导入 | ⭐⭐⭐⭐ 开发体验提升 |
| ES12/ES2021 | 2021 | 逻辑赋值、数字分隔符 | ⭐⭐⭐ 语法糖 |
| ES13/ES2022 | 2022 | 私有字段、顶层 await | ⭐⭐⭐⭐ 面向对象增强 |
| ES14/ES2023 | 2023 | findLast、Hashbang 支持 | ⭐⭐ 开发工具 |
| ES15/ES2024 | 2024 | RegExp v 模式、Array.fromAsync() | ⭐⭐⭐ 前沿特性 |
🚀 ES6 (2015):现代 JavaScript 的起点
ES6 是 JavaScript 历史上最重要的版本更新,奠定了现代前端开发的基础。
核心语法特性
1. let 和 const:块级作用域
// let:块级作用域变量
{
let name = 'JavaScript';
console.log(name); // 'JavaScript'
}
// console.log(name); // ReferenceError: name is not defined
// const:常量声明
const API_URL = 'https://api.example.com';
// API_URL = 'new-url'; // TypeError: Assignment to constant variable
// 对象/数组常量仍可修改内容
const user = { name: 'Alice' };
user.name = 'Bob'; // ✅ 允许
// user = {}; // ❌ 不允许
2. 箭头函数
// 传统函数
function add(a, b) {
return a + b;
}
// 箭头函数
const add = (a, b) => a + b;
// 单参数可省略括号
const double = x => x * 2;
// 多行函数体
const createUser = (name, age) => {
return {
name,
age,
createdAt: new Date()
};
};
// this 绑定特性
const obj = {
name: 'Object',
traditional: function() {
setTimeout(function() {
console.log(this.name); // undefined (this 指向全局)
}, 100);
},
arrow: function() {
setTimeout(() => {
console.log(this.name); // 'Object' (继承外层 this)
}, 100);
}
};3. 模板字符串
const name = 'World';
const age = 25;
// 基本插值
const message = `Hello, ${name}! You are ${age} years old.`;
// 多行字符串
const html = `
<div class="user">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
// 表达式计算
const price = 100;
const tax = 0.08;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;4. 解构赋值
// 数组解构
const [first, second, third] = [1, 2, 3];
const [, , , fourth] = [1, 2, 3, 4]; // 跳过前三个
// 对象解构
const user = { name: 'Alice', age: 30, city: 'New York' };
const { name, age } = user;
// 重命名
const { name: userName, age: userAge } = user;
// 默认值
const { name, country = 'Unknown' } = user;
// 嵌套解构
const data = {
user: {
profile: {
name: 'Bob',
email: '[email protected]'
}
}
};
const { user: { profile: { name, email } } } = data;
// 函数参数解构
function createUser({ name, age, city = 'Unknown' }) {
return { name, age, city };
}
const user1 = createUser({ name: 'Alice', age: 25 });5. 默认参数与扩展运算符
// 默认参数
function greet(name = 'Guest', message = 'Hello') {
return `${message}, ${name}!`;
}
// 扩展运算符 - 数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// 扩展运算符 - 对象
const user = { name: 'Alice', age: 30 };
const userWithRole = { ...user, role: 'admin' };
// 剩余参数
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
sum(1, 2, 3, 4, 5); // 15
// 函数调用中的扩展运算符
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3
6. 类 (Class)
// 基本类定义
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
return `Hello, I'm ${this.name}`;
}
// 静态方法
static createAdult(name) {
return new Person(name, 18);
}
}
// 继承
class Employee extends Person {
constructor(name, age, position) {
super(name, age); // 调用父类构造函数
this.position = position;
}
// 重写方法
greet() {
return `${super.greet()} and I'm a ${this.position}`;
}
work() {
return `${this.name} is working as a ${this.position}`;
}
}
const employee = new Employee('Alice', 28, 'Developer');
console.log(employee.greet()); // "Hello, I'm Alice and I'm a Developer"
7. 模块化 (Import/Export)
// math.js - 导出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
multiply(a, b) {
return a * b;
}
}
// 默认导出
export default function subtract(a, b) {
return a - b;
}
// main.js - 导入
import subtract from './math.js'; // 默认导入
import { PI, add, Calculator } from './math.js'; // 命名导入
import * as MathUtils from './math.js'; // 导入所有
// 使用
console.log(PI);
console.log(add(2, 3));
const calc = new Calculator();
console.log(calc.multiply(4, 5));
console.log(subtract(10, 3));8. Promise:异步编程基础
// 创建 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation successful!');
} else {
reject('Operation failed!');
}
}, 1000);
});
// 使用 Promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Operation completed'));
// Promise 链
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log('Data:', data);
return processData(data);
})
.then(processedData => {
console.log('Processed:', processedData);
})
.catch(error => {
console.error('Error:', error);
});
}
// Promise.all - 并行执行
const promise1 = fetch('/api/users');
const promise2 = fetch('/api/posts');
Promise.all([promise1, promise2])
.then(([users, posts]) => {
console.log('Users:', users);
console.log('Posts:', posts);
});
// Promise.race - 竞争执行
Promise.race([
fetch('/api/fast'),
fetch('/api/slow')
]).then(response => {
console.log('First response:', response);
});9. Map 和 Set:新的数据结构
// Map - 键值对集合
const map = new Map();
map.set('name', 'Alice');
map.set(42, 'The answer');
map.set({}, 'Object key');
const user = { id: 1 };
map.set(user, 'User object');
// 获取值
console.log(map.get('name')); // 'Alice'
console.log(map.get(42)); // 'The answer'
// 检查键是否存在
console.log(map.has('name')); // true
// 删除键
map.delete('name');
// 遍历 Map
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// Set - 唯一值集合
const set = new Set([1, 2, 3, 3, 4, 4, 5]);
console.log(set); // Set {1, 2, 3, 4, 5}
// 添加值
set.add(6);
set.add(1); // 重复值不会被添加
// 检查值是否存在
console.log(set.has(3)); // true
// 删除值
set.delete(3);
// 遍历 Set
for (const value of set) {
console.log(value);
}
// 实际应用:数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)]; // [1, 2, 3, 4, 5]
📈 ES7 (2016):小而实用的增强
ES7 虽然改动较小,但引入了两个非常实用的特性。
1. Array.prototype.includes()
const numbers = [1, 2, 3, 4, 5];
// 检查元素是否存在
console.log(numbers.includes(3)); // true
console.log(numbers.includes(6)); // false
// 支持起始位置
console.log(numbers.includes(3, 2)); // true (从索引2开始查找)
console.log(numbers.includes(1, 1)); // false (从索引1开始查找)
// 与 indexOf 的对比
// indexOf 返回位置,includes 返回布尔值
console.log(numbers.indexOf(3) !== -1); // true (旧方式)
console.log(numbers.includes(3)); // true (新方式,更直观)
// 特殊值处理
console.log([NaN].includes(NaN)); // true
console.log([NaN].indexOf(NaN) !== -1); // false (indexOf 无法正确处理 NaN)
2. 指数运算符 (**)
// 基本用法
console.log(2 ** 3); // 8 (2的3次方)
console.log(10 ** 2); // 100
// 与 Math.pow() 的对比
console.log(2 ** 3); // 8
console.log(Math.pow(2, 3)); // 8 (旧方式)
// 负指数
console.log(2 ** -2); // 0.25
// 组合运算
const base = 2;
const exponent = 3;
const result = base ** exponent; // 8
// 在表达式中的使用
const area = radius => Math.PI * radius ** 2;
console.log(area(5)); // 78.53981633974483
// 右结合性
console.log(2 ** 3 ** 2); // 512 (等同于 2 ** (3 ** 2))
🔄 ES8 (2017):异步编程新纪元
ES8 引入了 async/await,彻底改变了 JavaScript 异步编程的方式。
1. Async/Await
// 基本语法
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// 使用 async 函数
async function main() {
try {
const data = await fetchData();
console.log('Data:', data);
} catch (error) {
console.error('Failed to fetch data:', error);
}
}
// 箭头函数形式
const fetchUserData = async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
};
// 并行执行多个异步操作
async function fetchMultipleData() {
const [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 };
}
// 顺序执行异步操作
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// async 函数返回 Promise
async function getValue() {
return 42;
}
const promise = getValue(); // 返回 Promise
promise.then(value => console.log(value)); // 42
2. Object.values() 和 Object.entries()
const user = {
name: 'Alice',
age: 30,
city: 'New York',
email: '[email protected]'
};
// Object.values() - 获取所有值
const values = Object.values(user);
console.log(values); // ['Alice', 30, 'New York', '[email protected]']
// Object.entries() - 获取键值对数组
const entries = Object.entries(user);
console.log(entries);
// [
// ['name', 'Alice'],
// ['age', 30],
// ['city', 'New York'],
// ['email', '[email protected]']
// ]
// 实际应用:对象转换
const transformed = Object.entries(user).map(([key, value]) => ({
key,
value,
type: typeof value
}));
console.log(transformed);
// 对象过滤
const filteredEntries = Object.entries(user)
.filter(([key, value]) => typeof value === 'string')
.reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
console.log(filteredEntries); // { name: 'Alice', city: 'New York', email: '[email protected]' }
// 与 Map 的转换
const userMap = new Map(Object.entries(user));
console.log(userMap.get('name')); // 'Alice'
3. String.prototype.padStart() 和 padEnd()
// padStart() - 在字符串开头填充
const str = '5';
console.log(str.padStart(3, '0')); // '005'
const name = 'Alice';
console.log(name.padStart(10, '*')); // '*****Alice'
// padEnd() - 在字符串末尾填充
console.log(str.padEnd(3, '0')); // '500'
console.log(name.padEnd(10, '*')); // 'Alice*****'
// 实际应用:数字格式化
function formatNumber(num, length = 2) {
return String(num).padStart(length, '0');
}
console.log(formatNumber(5)); // '05'
console.log(formatNumber(12)); // '12'
console.log(formatNumber(123)); // '123' (超过长度不截断)
// 表格对齐
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 }
];
users.forEach(user => {
const namePadded = user.name.padEnd(10, ' ');
const agePadded = String(user.age).padStart(3, ' ');
console.log(`${namePadded} | ${agePadded}`);
});
// Alice | 30
// Bob | 25
// Charlie | 35
🎯 ES9 (2018):对象与异步迭代
ES9 继续完善 JavaScript 的语法特性,特别是在对象操作和异步编程方面。
1. 对象扩展运算符
// 对象扩展
const user = { name: 'Alice', age: 30 };
const additionalInfo = { city: 'New York', country: 'USA' };
const completeUser = { ...user, ...additionalInfo };
console.log(completeUser);
// { name: 'Alice', age: 30, city: 'New York', country: 'USA' }
// 属性覆盖
const updatedUser = { ...user, age: 31 };
console.log(updatedUser); // { name: 'Alice', age: 31 }
// 浅拷贝
const userCopy = { ...user };
console.log(userCopy === user); // false (不同对象)
// 嵌套对象注意事项
const original = {
user: { name: 'Alice', age: 30 },
settings: { theme: 'dark' }
};
const copy = { ...original };
copy.user.name = 'Bob';
console.log(original.user.name); // 'Bob' (嵌套对象仍然是引用)
// 深拷贝需要其他方法
const deepCopy = JSON.parse(JSON.stringify(original));
// 在函数中使用
function updateUser(updates) {
const defaultUser = { name: 'Guest', age: 0, active: true };
return { ...defaultUser, ...updates };
}
const newUser = updateUser({ name: 'Alice', age: 25 });
console.log(newUser); // { name: 'Alice', age: 25, active: true }
2. 异步迭代 (for-await-of)
// 异步迭代器
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
// 使用 for-await-of
async function processAsyncData() {
for await (const value of asyncGenerator()) {
console.log(value); // 1, 2, 3
}
}
// 异步可迭代对象
const asyncIterable = {
async *[Symbol.asyncIterator]() {
let i = 1;
while (i <= 3) {
yield new Promise(resolve => {
setTimeout(() => resolve(i * 2), 1000);
});
i++;
}
}
};
async function processAsyncIterable() {
for await (const value of asyncIterable) {
console.log(value); // 2, 4, 6 (每秒输出一个)
}
}
// 实际应用:分页数据获取
async function* fetchPages(url) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items;
hasMore = data.hasMore;
page++;
}
}
async function getAllItems(url) {
const allItems = [];
for await (const items of fetchPages(url)) {
allItems.push(...items);
}
return allItems;
}3. Promise.prototype.finally()
// 基本用法
fetch('/api/data')
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error))
.finally(() => console.log('Request completed'));
// 实际应用:加载状态管理
function showLoading() {
console.log('Loading...');
}
function hideLoading() {
console.log('Loading complete');
}
async function fetchDataWithLoading() {
showLoading();
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
throw error;
} finally {
hideLoading(); // 无论成功失败都会执行
}
}
// 资源清理
function withResource(resource, callback) {
return resource
.then(callback)
.catch(error => {
console.error('Error:', error);
throw error;
})
.finally(() => {
// 清理资源
resource.close && resource.close();
});
}🛠️ ES10 (2019):数组与对象的便利增强
ES10 引入了许多实用的数组方法和对象操作功能。
1. Array.prototype.flat() 和 flatMap()
// flat() - 数组扁平化
const nestedArray = [1, [2, 3], [4, [5, 6]]];
console.log(nestedArray.flat()); // [1, 2, 3, [4, [5, 6]]]
// 指定扁平化深度
console.log(nestedArray.flat(2)); // [1, 2, 3, 4, 5, 6]
// 完全扁平化
const deeplyNested = [1, [2, [3, [4, [5]]]]];
console.log(deeplyNested.flat(Infinity)); // [1, 2, 3, 4, 5]
// flatMap() - 映射并扁平化
const sentences = [
'Hello world',
'How are you',
'Fine thank you'
];
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words);
// ['Hello', 'world', 'How', 'are', 'you', 'Fine', 'thank', 'you']
// 与 map + flat 的对比
const result1 = sentences.map(s => s.split(' ')).flat();
const result2 = sentences.flatMap(s => s.split(' '));
console.log(result1.length === result2.length); // true
// 实际应用:数据转换
const data = [
{ id: 1, tags: ['javascript', 'nodejs'] },
{ id: 2, tags: ['react', 'javascript'] },
{ id: 3, tags: ['vue', 'nodejs'] }
];
const allTags = data.flatMap(item => item.tags);
const uniqueTags = [...new Set(allTags)];
console.log(uniqueTags); // ['javascript', 'nodejs', 'react', 'vue']
// 复杂数据处理
const orders = [
{ id: 1, items: [{ name: 'Book', price: 20 }, { name: 'Pen', price: 5 }] },
{ id: 2, items: [{ name: 'Laptop', price: 1000 }] }
];
const allItems = orders.flatMap(order =>
order.items.map(item => ({ ...item, orderId: order.id }))
);
console.log(allItems);
// [
// { name: 'Book', price: 20, orderId: 1 },
// { name: 'Pen', price: 5, orderId: 1 },
// { name: 'Laptop', price: 1000, orderId: 2 }
// ]
2. Object.fromEntries()
// 基本用法:将键值对数组转换为对象
const entries = [
['name', 'Alice'],
['age', 30],
['city', 'New York']
];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: 'Alice', age: 30, city: 'New York' }
// 与 Object.entries() 的配合使用
const original = { name: 'Bob', age: 25, city: 'London' };
const transformed = Object.fromEntries(
Object.entries(original).map(([key, value]) => [
key.toUpperCase(),
typeof value === 'string' ? value.toUpperCase() : value
])
);
console.log(transformed);
// { NAME: 'BOB', AGE: 25, CITY: 'LONDON' }
// 实际应用:查询参数处理
const queryString = 'name=Alice&age=30&city=New+York';
const params = new URLSearchParams(queryString);
const paramsObj = Object.fromEntries(params);
console.log(paramsObj); // { name: 'Alice', age: '30', city: 'New York' }
// 对象过滤
const user = {
name: 'Alice',
age: 30,
password: 'secret123',
email: '[email protected]',
token: 'abc123'
};
const publicUser = Object.fromEntries(
Object.entries(user).filter(([key]) =>
!['password', 'token'].includes(key)
)
);
console.log(publicUser);
// { name: 'Alice', age: 30, email: '[email protected]' }
// Map 转换为对象
const map = new Map([
['name', 'Charlie'],
['age', 35]
]);
const mapObj = Object.fromEntries(map);
console.log(mapObj); // { name: 'Charlie', age: 35 }
3. String.prototype.trimStart() 和 trimEnd()
const message = ' Hello, World! ';
// trimStart() - 去除开头空白
console.log(message.trimStart()); // 'Hello, World! '
// trimEnd() - 去除末尾空白
console.log(message.trimEnd()); // ' Hello, World!'
// trim() - 去除两端空白
console.log(message.trim()); // 'Hello, World!'
// 别名方法
console.log(message.trimLeft() === message.trimStart()); // true
console.log(message.trimRight() === message.trimEnd()); // true
// 实际应用:用户输入处理
function cleanInput(input) {
return input.trimStart().trimEnd();
}
const userInput = ' [email protected] ';
const cleanEmail = cleanInput(userInput);
console.log(cleanEmail); // '[email protected]'
// 代码格式化
function formatCode(code) {
return code
.split('\n')
.map(line => line.trimStart())
.join('\n');
}
const messyCode = `
function test() {
console.log('Hello');
}
`;
console.log(formatCode(messyCode));
// function test() {
// console.log('Hello');
// }
4. 可选的 catch 绑定
// 传统方式:必须指定错误参数
try {
JSON.parse('invalid json');
} catch (error) {
console.log('Something went wrong');
}
// ES10:可以省略错误参数
try {
JSON.parse('invalid json');
} catch {
console.log('Something went wrong');
}
// 实际应用:错误处理简化
function safeParse(jsonString) {
try {
return JSON.parse(jsonString);
} catch {
return null;
}
}
console.log(safeParse('{"name": "Alice"}')); // { name: 'Alice' }
console.log(safeParse('invalid json')); // null
// 多个 try-catch 块
async function processFiles(files) {
const results = [];
for (const file of files) {
try {
const content = await readFile(file);
const parsed = JSON.parse(content);
results.push(parsed);
} catch {
// 忽略错误,继续处理下一个文件
console.log(`Failed to process ${file}`);
}
}
return results;
}🔍 ES11 (2020):可选链与空值合并
ES11 引入了许多提升开发体验的重要特性。
1. 可选链操作符 (?.)
const user = {
name: 'Alice',
address: {
street: '123 Main St',
city: 'New York'
}
};
// 传统方式:多层检查
const city = user && user.address && user.address.city;
console.log(city); // 'New York'
// 可选链:简洁安全
const city2 = user?.address?.city;
console.log(city2); // 'New York'
// 处理不存在的属性
const country = user?.address?.country;
console.log(country); // undefined
// 数组可选链
const users = [
{ name: 'Alice', posts: [{ title: 'Post 1' }] },
{ name: 'Bob' }
];
const firstPostTitle = users[0]?.posts?.[0]?.title;
console.log(firstPostTitle); // 'Post 1'
const secondPostTitle = users[1]?.posts?.[0]?.title;
console.log(secondPostTitle); // undefined
// 函数调用可选链
const userWithMethod = {
name: 'Alice',
greet: function() {
return 'Hello!';
}
};
const greeting = userWithMethod.greet?.();
console.log(greeting); // 'Hello!'
const noGreeting = userWithMethod.sayGoodbye?.();
console.log(noGreeting); // undefined
// 实际应用:API 响应处理
function getUserCity(user) {
return user?.profile?.address?.city ?? 'Unknown';
}
console.log(getUserCity({ profile: { address: { city: 'Beijing' } } })); // 'Beijing'
console.log(getUserCity({ profile: {} })); // 'Unknown'
console.log(getUserCity(null)); // 'Unknown'
2. 空值合并操作符 (??)
// 基本用法:仅在 null 或 undefined 时使用默认值
const name = null ?? 'Guest';
console.log(name); // 'Guest'
const age = undefined ?? 18;
console.log(age); // 18
const active = false ?? true;
console.log(active); // false (false 不是 null 或 undefined)
const count = 0 ?? 10;
console.log(count); // 0 (0 不是 null 或 undefined)
// 与 || 操作符的对比
const value1 = 0 || 10; // 10 (0 是 falsy)
const value2 = 0 ?? 10; // 0 (0 不是 null/undefined)
const value3 = '' || 'default'; // 'default' (空字符串是 falsy)
const value4 = '' ?? 'default'; // '' (空字符串不是 null/undefined)
// 实际应用:配置默认值
const config = {
timeout: null,
retries: undefined,
enabled: false,
port: 0
};
const finalConfig = {
timeout: config.timeout ?? 5000, // 5000
retries: config.retries ?? 3, // 3
enabled: config.enabled ?? true, // false (保持原值)
port: config.port ?? 3000 // 0 (保持原值)
};
// 函数参数默认值
function createUser(options = {}) {
return {
name: options.name ?? 'Guest',
age: options.age ?? 0,
email: options.email ?? null,
active: options.active ?? true
};
}
const user1 = createUser({ name: 'Alice', age: 0, active: false });
console.log(user1);
// { name: 'Alice', age: 0, email: null, active: false }
// 嵌套使用
const data = {
user: {
profile: null
}
};
const bio = data.user?.profile?.bio ?? 'No bio available';
console.log(bio); // 'No bio available'
3. 动态 import()
// 基本动态导入
async function loadModule() {
const module = await import('./math.js');
console.log(module.add(2, 3)); // 5
}
// 条件导入
async function loadFeature(featureName) {
if (featureName === 'charts') {
const charts = await import('./charts.js');
charts.init();
} else if (featureName === 'maps') {
const maps = await import('./maps.js');
maps.init();
}
}
// 基于用户交互的懒加载
document.getElementById('loadCharts').addEventListener('click', async () => {
const charts = await import('./charts.js');
charts.renderChart();
});
// 路由级别的代码分割
const routes = {
home: () => import('./pages/home.js'),
about: () => import('./pages/about.js'),
contact: () => import('./pages/contact.js')
};
async function loadPage(pageName) {
try {
const pageModule = await routes[pageName]();
pageModule.default.render();
} catch (error) {
console.error(`Failed to load ${pageName}:`, error);
}
}
// 动态导入多个模块
async function loadUtils() {
const [dateUtils, stringUtils, mathUtils] = await Promise.all([
import('./utils/date.js'),
import('./utils/string.js'),
import('./utils/math.js')
]);
return { dateUtils, stringUtils, mathUtils };
}4. BigInt
// 创建 BigInt
const bigInt1 = 123n; // 字面量方式
const bigInt2 = BigInt(123); // 构造函数方式
const bigInt3 = BigInt('123456789012345678901234567890');
// 基本运算
const a = 123n;
const b = 456n;
console.log(a + b); // 579n
console.log(a * b); // 56088n
console.log(a / b); // 0n (整数除法)
console.log(a % b); // 123n
// 与普通数字的比较
console.log(123n === 123); // false (类型不同)
console.log(123n == 123); // true (值相等)
// 大数计算
const hugeNumber = BigInt('9007199254740991'); // 超过 Number.MAX_SAFE_INTEGER
const result = hugeNumber * 2n;
console.log(result); // 18014398509481982n
// 实际应用:精确计算
function preciseCalculation(a, b) {
return BigInt(a) * BigInt(b);
}
const result1 = preciseCalculation('12345678901234567890', '98765432109876543210');
console.log(result1.toString()); // '1219326311370217952237463801111263526900'
// JSON 序列化支持(需要自定义)
const data = { bigNumber: 123n };
const json = JSON.stringify(data, (key, value) =>
typeof value === 'bigint' ? value.toString() + 'n' : value
);
console.log(json); // '{"bigNumber":"123n"}'
5. Promise.allSettled()
// 基本用法
const promises = [
Promise.resolve(1),
Promise.reject('Error 1'),
Promise.resolve(3),
Promise.reject('Error 2')
];
Promise.allSettled(promises).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index}: ${result.value}`);
} else {
console.log(`Promise ${index}: ${result.reason}`);
}
});
});
// 与 Promise.all 的对比
async function comparePromiseMethods() {
const promises = [
Promise.resolve('Success 1'),
Promise.reject('Error'),
Promise.resolve('Success 2')
];
try {
// Promise.all - 任一失败则整体失败
const allResults = await Promise.all(promises);
console.log('All succeeded:', allResults);
} catch (error) {
console.log('Promise.all failed:', error);
}
// Promise.allSettled - 等待所有完成
const settledResults = await Promise.allSettled(promises);
console.log('All settled:', settledResults);
}
// 实际应用:批量操作
async function uploadFiles(files) {
const uploadPromises = files.map(file =>
uploadFile(file).catch(error => ({ error, file }))
);
const results = await Promise.allSettled(uploadPromises);
const successful = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failed = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
return { successful, failed };
}
// 数据获取:即使部分失败也返回成功的数据
async function fetchMultipleAPIs() {
const apis = [
'https://api.example.com/users',
'https://api.example.com/posts',
'https://api.example.com/comments'
];
const results = await Promise.allSettled(
apis.map(url => fetch(url).then(r => r.json()))
);
return results.reduce((acc, result, index) => {
const apiName = apis[index].split('/').pop();
acc[apiName] = result.status === 'fulfilled' ? result.value : null;
return acc;
}, {});
}🎨 ES12 (2021):简化语法与可读性提升
ES12 引入了许多语法糖,让代码更加简洁易读。
1. 逻辑赋值运算符
// 逻辑与赋值 (&&=)
let user = { name: 'Alice', age: 30 };
user.age &&= user.age > 18 ? 'adult' : 'minor';
console.log(user.age); // 'adult'
// 等价于
// if (user.age) {
// user.age = user.age > 18 ? 'adult' : 'minor';
// }
// 逻辑或赋值 (||=)
let settings = { theme: 'dark' };
settings.language ||= 'en';
console.log(settings.language); // 'en'
// 等价于
// settings.language = settings.language || 'en';
// 空值合并赋值 (??=)
let config = { timeout: null };
config.timeout ??= 5000;
console.log(config.timeout); // 5000
// 等价于
// if (config.timeout === null || config.timeout === undefined) {
// config.timeout = 5000;
// }
// 实际应用:配置更新
function updateConfig(currentConfig, newConfig) {
// 只更新非空值
newConfig.theme &&= (currentConfig.theme = newConfig.theme);
newConfig.language ??= (currentConfig.language = newConfig.language);
newConfig.timeout ||= (currentConfig.timeout = newConfig.timeout);
return currentConfig;
}
const current = { theme: 'light', language: null, timeout: 0 };
const updates = { theme: 'dark', language: 'zh', timeout: null };
const updated = updateConfig(current, updates);
console.log(updated);
// { theme: 'dark', language: 'zh', timeout: 0 }
// 对象属性的条件更新
const user = { name: 'Alice', email: null, age: 25 };
user.email ??= '[email protected]'; // 设置默认邮箱
user.age &&= user.age >= 18 ? 'adult' : 'minor'; // 根据年龄设置状态
console.log(user);
// { name: 'Alice', email: '[email protected]', age: 'adult' }
2. 数字分隔符
// 基本用法:使用下划线分隔数字
const billion = 1_000_000_000;
const million = 1_000_000;
const thousand = 1_000;
console.log(billion); // 1000000000
console.log(million); // 1000000
// 小数部分
const pi = 3.141_592_653;
console.log(pi); // 3.141592653
// 二进制、八进制、十六进制
const binary = 0b1010_1011_1100_1101;
const octal = 0o12_34_56;
const hex = 0xAB_CD_EF;
console.log(binary); // 43981
console.log(octal); // 34230
console.log(hex); // 11259375
// 实际应用:提高可读性
const price = 1_299_99; // $1,299.99
const phoneNumber = 138_0013_8000; // 手机号
const macAddress = 0xAA_BB_CC_DD_EE_FF; // MAC地址
// 科学计数法
const largeNumber = 1.234e5_678; // 123400000
console.log(largeNumber);
// 注意事项:不能连续使用分隔符
// const invalid = 123__456; // SyntaxError
// const invalid2 = 123_; // SyntaxError
// const invalid3 = _123; // SyntaxError
// 在计算中的使用
const calculation = (1_000_000 * 2) + (500_000 / 10);
console.log(calculation); // 2050000
3. String.prototype.replaceAll()
// 基本用法
const message = 'Hello world, hello universe, hello galaxy';
const replaced = message.replaceAll('hello', 'hi');
console.log(replaced); // 'Hi world, hi universe, hi galaxy'
// 区分大小写
const caseSensitive = 'Hello hello HELLO';
console.log(caseSensitive.replaceAll('hello', 'hi')); // 'Hello hi HELLO'
// 使用正则表达式(需要全局标志)
const regexReplaced = caseSensitive.replaceAll(/hello/gi, 'hi');
console.log(regexReplaced); // 'hi hi hi'
// 与 replace() 的对比
const text = 'apple, banana, apple, orange';
console.log(text.replace('apple', 'grape')); // 'grape, banana, apple, orange'
console.log(text.replaceAll('apple', 'grape')); // 'grape, banana, grape, orange'
// 实际应用:模板变量替换
function renderTemplate(template, data) {
let result = template;
for (const [key, value] of Object.entries(data)) {
const placeholder = `{{${key}}}`;
result = result.replaceAll(placeholder, value);
}
return result;
}
const template = 'Hello {{name}}, you have {{count}} new messages. Welcome to {{platform}}!';
const data = { name: 'Alice', count: 5, platform: 'OurApp' };
const rendered = renderTemplate(template, data);
console.log(rendered);
// 'Hello Alice, you have 5 new messages. Welcome to OurApp!'
// 代码清理
function cleanCode(code) {
return code
.replaceAll(/\t/g, ' ') // 制表符转空格
.replaceAll(/\r\n/g, '\n') // Windows换行符转Unix
.replaceAll(/ +/g, ' ') // 多个空格转单个
.trim();
}
const messyCode = 'function test() {\t\treturn "hello";\r\n\r\n}';
console.log(cleanCode(messyCode));
// 'function test() { return "hello"; }'
4. Promise.any()
// 基本用法:返回第一个成功的 Promise
const promises = [
Promise.reject('Error 1'),
Promise.resolve('Success 1'),
Promise.reject('Error 2'),
Promise.resolve('Success 2')
];
Promise.any(promises).then(result => {
console.log(result); // 'Success 1' (第一个成功的)
}).catch(error => {
console.error(error);
});
// 所有 Promise 都失败时
const failedPromises = [
Promise.reject('Error 1'),
Promise.reject('Error 2'),
Promise.reject('Error 3')
];
Promise.any(failedPromises).catch(error => {
console.log(error); // AggregateError: All promises were rejected
console.log(error.errors); // ['Error 1', 'Error 2', 'Error 3']
});
// 实际应用:多个 API 源
async function fetchFromMultipleSources() {
const sources = [
fetch('https://api1.example.com/data').then(r => r.json()),
fetch('https://api2.example.com/data').then(r => r.json()),
fetch('https://api3.example.com/data').then(r => r.json())
];
try {
const data = await Promise.any(sources);
return data;
} catch (error) {
throw new Error('All data sources failed');
}
}
// 图片加载:使用第一个成功加载的图片
async function loadFirstAvailableImage(urls) {
const imagePromises = urls.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = reject;
img.src = url;
});
});
try {
const firstLoadedUrl = await Promise.any(imagePromises);
return firstLoadedUrl;
} catch (error) {
throw new Error('No images could be loaded');
}
}
// 与其他 Promise 方法的对比
const promises2 = [
Promise.resolve('A'),
Promise.resolve('B'),
Promise.resolve('C')
];
// Promise.any: 第一个成功
Promise.any(promises2).then(x => console.log('any:', x)); // 'A'
// Promise.race: 第一个完成(成功或失败)
Promise.race(promises2).then(x => console.log('race:', x)); // 'A'
// Promise.all: 所有成功
Promise.all(promises2).then(x => console.log('all:', x)); // ['A', 'B', 'C']
🔒 ES13 (2022):类私有属性与顶层 await
ES13 在面向对象编程和模块系统方面带来了重要改进。
1. 私有字段和私有方法
class BankAccount {
// 私有字段(以 # 开头)
#balance = 0;
#accountNumber;
#transactions = [];
constructor(accountNumber, initialBalance = 0) {
this.#accountNumber = accountNumber;
this.#balance = initialBalance;
}
// 公共方法
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#addTransaction('deposit', amount);
return true;
}
return false;
}
withdraw(amount) {
if (amount > 0 && this.#balance >= amount) {
this.#balance -= amount;
this.#addTransaction('withdraw', amount);
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
// 私有方法
#addTransaction(type, amount) {
this.#transactions.push({
type,
amount,
timestamp: new Date(),
balance: this.#balance
});
}
#validateTransaction(amount) {
return amount > 0 && Number.isFinite(amount);
}
// 私有 getter
get #transactionCount() {
return this.#transactions.length;
}
printStatement() {
console.log(`Account: ${this.#accountNumber}`);
console.log(`Balance: $${this.#balance}`);
console.log(`Transactions: ${this.#transactionCount}`);
this.#transactions.forEach(t => {
console.log(`${t.type}: $${t.amount} at ${t.timestamp}`);
});
}
}
// 使用示例
const account = new BankAccount('12345', 1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance()); // 1300
account.printStatement();
// 私有字段无法从外部访问
// console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
// console.log(account.#accountNumber); // SyntaxError
// 继承中的私有字段
class SavingsAccount extends BankAccount {
#interestRate;
constructor(accountNumber, initialBalance, interestRate) {
super(accountNumber, initialBalance);
this.#interestRate = interestRate;
}
applyInterest() {
const interest = this.getBalance() * this.#interestRate;
// this.#balance += interest; // 错误:无法访问父类的私有字段
this.deposit(interest); // 通过公共方法访问
}
}
// 静态私有字段
class Counter {
static #instanceCount = 0;
static #instances = [];
constructor() {
Counter.#instanceCount++;
Counter.#instances.push(this);
}
static getInstanceCount() {
return Counter.#instanceCount;
}
static getAllInstances() {
return [...Counter.#instances];
}
}
const c1 = new Counter();
const c2 = new Counter();
console.log(Counter.getInstanceCount()); // 2
2. 顶层 await
// 在模块顶层直接使用 await
// config.js
const response = await fetch('/api/config');
const config = await response.json();
export const apiUrl = config.apiUrl;
export const timeout = config.timeout;
// 使用该模块时,会等待配置加载完成
// main.js
import { apiUrl, timeout } from './config.js';
console.log('API URL:', apiUrl); // 配置已加载完成
console.log('Timeout:', timeout);
// 实际应用:数据库连接初始化
// database.js
const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();
const db = client.db('myapp');
export const users = db.collection('users');
export const posts = db.collection('posts');
// 动态模块加载
// dynamic-module.js
const moduleData = await import('./data.json');
export const data = moduleData.default;
// 条件模块导入
// conditional-import.js
if (process.env.NODE_ENV === 'development') {
const devTools = await import('./dev-tools.js');
devTools.init();
}
export const isDevelopment = process.env.NODE_ENV === 'development';
// 异步初始化的模式
// app.js
class App {
static async initialize() {
const app = new App();
await app.loadConfig();
await app.connectDatabase();
await app.setupRoutes();
return app;
}
async loadConfig() {
const response = await fetch('/config.json');
this.config = await response.json();
}
async connectDatabase() {
// 数据库连接逻辑
}
async setupRoutes() {
// 路由设置逻辑
}
}
export const app = await App.initialize();
// 错误处理
// 在顶层 await 中,错误会阻止模块加载
// error-handling.js
let config;
try {
const response = await fetch('/config.json');
config = await response.json();
} catch (error) {
console.error('Failed to load config:', error);
config = { apiUrl: 'http://localhost:3000', timeout: 5000 };
}
export default config;3. 类静态初始化块
class DataProcessor {
static data = [];
static config = {};
// 静态初始化块
static {
console.log('Initializing DataProcessor...');
// 复杂的静态初始化逻辑
try {
const response = fetchSync('/api/config');
this.config = response.json();
} catch (error) {
this.config = { timeout: 5000, retries: 3 };
}
// 初始化数据
this.data = this.loadInitialData();
}
static loadInitialData() {
// 模拟数据加载
return [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
];
}
static {
console.log('DataProcessor initialized with', this.data.length, 'items');
}
}
// 多个静态块
class ComplexClass {
static property1;
static property2;
static {
console.log('First static block');
this.property1 = 'value1';
}
static {
console.log('Second static block');
this.property2 = 'value2';
}
static {
// 可以访问前面设置的属性
console.log('Property1:', this.property1);
console.log('Property2:', this.property2);
}
}
// 异步静态初始化(需要使用顶层 await)
// async-static-init.js
class AsyncInitializer {
static data;
static initialized = false;
static {
// 注意:静态块本身不能是异步的
// 但可以使用顶层 await 来实现异步初始化
this.init();
}
static async init() {
try {
const response = await fetch('/api/data');
this.data = await response.json();
this.initialized = true;
} catch (error) {
console.error('Initialization failed:', error);
this.data = [];
this.initialized = false;
}
}
}
// 等待初始化完成
await AsyncInitializer.init();4. 错误原因 (Error Cause)
// 基本用法:为错误添加原因
function processData(data) {
if (!data) {
throw new Error('Data is required', { cause: 'input_validation' });
}
try {
return JSON.parse(data);
} catch (parseError) {
throw new Error('Invalid JSON format', {
cause: parseError
});
}
}
try {
processData('invalid json');
} catch (error) {
console.log(error.message); // 'Invalid JSON format'
console.log(error.cause); // 原始的 JSON 解析错误
}
// 实际应用:API 错误处理
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`, {
cause: {
status: response.status,
statusText: response.statusText,
url: response.url
}
});
}
return await response.json();
} catch (error) {
if (error.cause) {
// 包装已有错误
throw new Error(`Failed to fetch user ${userId}`, {
cause: error
});
}
throw error;
}
}
// 错误链追踪
function validateUser(user) {
if (!user.email) {
throw new Error('Email is required', { cause: 'validation_error' });
}
if (!user.age) {
throw new Error('Age is required', { cause: 'validation_error' });
}
if (user.age < 18) {
throw new Error('User must be 18 or older', { cause: 'age_validation' });
}
}
try {
validateUser({ name: 'Alice', age: 16 });
} catch (error) {
console.log('Error:', error.message);
console.log('Cause:', error.cause);
// 错误链
let currentError = error;
while (currentError) {
console.log('->', currentError.message);
currentError = currentError.cause;
}
}
// 自定义错误类
class ApplicationError extends Error {
constructor(message, code, cause) {
super(message, { cause });
this.name = 'ApplicationError';
this.code = code;
}
}
class DatabaseError extends ApplicationError {
constructor(operation, cause) {
super(`Database operation failed: ${operation}`, 'DATABASE_ERROR', cause);
this.name = 'DatabaseError';
this.operation = operation;
}
}
// 使用自定义错误
try {
// 模拟数据库错误
throw new Error('Connection timeout');
} catch (dbError) {
throw new DatabaseError('user_lookup', dbError);
}🔧 ES14 (2023):数组查找与脚本改进
ES14 引入了一些实用的数组方法和脚本执行改进。
1. Array.prototype.findLast() 和 findLastIndex()
// findLast() - 从后向前查找匹配的元素
const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
const lastEven = numbers.findLast(num => num % 2 === 0);
console.log(lastEven); // 4 (最后一个偶数)
const lastGreaterThanThree = numbers.findLast(num => num > 3);
console.log(lastGreaterThanThree); // 4 (最后一个大于3的数)
// findLastIndex() - 从后向前查找匹配元素的索引
const lastEvenIndex = numbers.findLastIndex(num => num % 2 === 0);
console.log(lastEvenIndex); // 5 (索引为5的元素是最后一个偶数)
const lastGreaterThanThreeIndex = numbers.findLastIndex(num => num > 3);
console.log(lastGreaterThanThreeIndex); // 5
// 与 find() 和 findIndex() 的对比
const firstEven = numbers.find(num => num % 2 === 0);
const firstEvenIndex = numbers.findIndex(num => num % 2 === 0);
console.log('First even:', firstEven, 'at index', firstEvenIndex); // 2 at 1
console.log('Last even:', lastEven, 'at index', lastEvenIndex); // 4 at 5
// 实际应用:查找最近的匹配项
const logs = [
{ timestamp: '2023-01-01T10:00:00', level: 'info', message: 'App started' },
{ timestamp: '2023-01-01T10:05:00', level: 'warn', message: 'Low memory' },
{ timestamp: '2023-01-01T10:10:00', level: 'error', message: 'Database connection failed' },
{ timestamp: '2023-01-01T10:15:00', level: 'info', message: 'Retrying connection' },
{ timestamp: '2023-01-01T10:20:00', level: 'error', message: 'Connection failed again' }
];
// 查找最后一个错误日志
const lastError = logs.findLast(log => log.level === 'error');
console.log(lastError);
// { timestamp: '2023-01-01T10:20:00', level: 'error', message: 'Connection failed again' }
// 查找最后一个警告之后的第一个错误
const lastWarnIndex = logs.findLastIndex(log => log.level === 'warn');
const errorAfterLastWarn = logs.slice(lastWarnIndex + 1).find(log => log.level === 'error');
console.log(errorAfterLastWarn);
// { timestamp: '2023-01-01T10:20:00', level: 'error', message: 'Connection failed again' }
// 历史记录管理
const history = [
{ action: 'add', item: 'A', timestamp: 1 },
{ action: 'remove', item: 'B', timestamp: 2 },
{ action: 'add', item: 'C', timestamp: 3 },
{ action: 'remove', item: 'D', timestamp: 4 },
{ action: 'add', item: 'E', timestamp: 5 }
];
// 查找最后一个添加操作
const lastAdd = history.findLast(entry => entry.action === 'add');
console.log(lastAdd); // { action: 'add', item: 'E', timestamp: 5 }
// 查找最后一个删除操作
const lastRemove = history.findLast(entry => entry.action === 'remove');
console.log(lastRemove); // { action: 'remove', item: 'D', timestamp: 4 }
2. Hashbang 语法支持
// 在 JavaScript 文件开头使用 Hashbang
#!/usr/bin/env node
// my-script.js
console.log('Hello from Node.js script!');
// 可以直接在 Unix 系统中执行
// ./my-script.js
// 带参数的脚本
#!/usr/bin/env node
const args = process.argv.slice(2);
console.log('Arguments:', args);
if (args.includes('--help')) {
console.log('Usage: ./my-script.js [options]');
process.exit(0);
}
// 实际应用:命令行工具
#!/usr/bin/env node
import { readFile, writeFile } from 'fs/promises';
import { resolve } from 'path';
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: ./file-processor.js <input> <output>');
process.exit(1);
}
const [inputFile, outputFile] = args;
async function processFile() {
try {
const content = await readFile(resolve(inputFile), 'utf8');
const processed = content.toUpperCase();
await writeFile(resolve(outputFile), processed);
console.log(`File processed: ${inputFile} -> ${outputFile}`);
} catch (error) {
console.error('Error processing file:', error.message);
process.exit(1);
}
}
processFile();
// 在 package.json 中配置
{
"bin": {
"my-tool": "./bin/my-tool.js"
}
}
// ./bin/my-tool.js
#!/usr/bin/env node
import cli from '../src/cli.js';
cli(process.argv);3. WeakMap 支持 Symbol 作为键
// ES14 允许使用 Symbol 作为 WeakMap 的键
const weakMap = new WeakMap();
const symbol1 = Symbol('description1');
const symbol2 = Symbol('description2');
const obj1 = { name: 'Object 1' };
const obj2 = { name: 'Object 2' };
// 使用 Symbol 作为键
weakMap.set(symbol1, obj1);
weakMap.set(symbol2, obj2);
console.log(weakMap.get(symbol1)); // { name: 'Object 1' }
console.log(weakMap.get(symbol2)); // { name: 'Object 2' }
// 检查是否存在
console.log(weakMap.has(symbol1)); // true
console.log(weakMap.has(Symbol('description1'))); // false (不同的 Symbol)
// 删除
weakMap.delete(symbol1);
console.log(weakMap.has(symbol1)); // false
// 实际应用:私有数据存储
class PrivateData {
constructor() {
this.#privateData = new WeakMap();
}
#privateData;
setPrivateData(obj, data) {
const key = Symbol.for(obj.constructor.name);
this.#privateData.set(key, data);
}
getPrivateData(obj) {
const key = Symbol.for(obj.constructor.name);
return this.#privateData.get(key);
}
}
// 元数据管理
const metadata = new WeakMap();
function addMetadata(target, key, value) {
const symbolKey = Symbol(key);
metadata.set(symbolKey, value);
metadata.set(target, symbolKey);
}
function getMetadata(target, key) {
const symbolKey = metadata.get(target);
return symbolKey && metadata.get(symbolKey);
}
// 使用示例
const user = { name: 'Alice' };
addMetadata(user, 'permissions', ['read', 'write']);
console.log(getMetadata(user, 'permissions')); // ['read', 'write']
🚀 ES15 (2024):现代正则与异步数组
ES15 继续完善 JavaScript 的功能,特别是在正则表达式和异步操作方面。
1. RegExp v 模式(Unicode 属性转义)
// 使用 v 标志启用 Unicode 属性转义
const regex = /\p{Script=Greek}/v;
console.log(regex.test('α')); // true (希腊字母)
console.log(regex.test('a')); // false (拉丁字母)
// 字符串属性
const letterRegex = /\p{L}/v; // 任何字母
console.log(letterRegex.test('A')); // true
console.log(letterRegex.test('α')); // true
console.log(letterRegex.test('1')); // false
const numberRegex = /\p{N}/v; // 任何数字
console.log(numberRegex.test('1')); // true
console.log(numberRegex.test('١')); // true (阿拉伯数字)
console.log(numberRegex.test('一')); // false (中文数字不是 Unicode 数字)
// 脚本属性
const chineseRegex = /\p{Script=Han}/v; // 中文字符
console.log(chineseRegex.test('中')); // true
console.log(chineseRegex.test('国')); // true
console.log(chineseRegex.test('a')); // false
const emojiRegex = /\p{Emoji}/v; // 表情符号
console.log(emojiRegex.test('😀')); // true
console.log(emojiRegex.test('👍')); // true
console.log(emojiRegex.test('a')); // false
// 组合属性
const chineseLetterRegex = /\p{Script=Han}&\p{L}/v; // 中文字母
console.log(chineseLetterRegex.test('中')); // true
// 实际应用:国际化文本处理
function detectLanguage(text) {
const scripts = {
Latin: /\p{Script=Latin}/v,
Greek: /\p{Script=Greek}/v,
Cyrillic: /\p{Script=Cyrillic}/v,
Han: /\p{Script=Han}/v,
Arabic: /\p{Script=Arabic}/v
};
for (const [script, regex] of Object.entries(scripts)) {
if (regex.test(text)) {
return script;
}
}
return 'Unknown';
}
console.log(detectLanguage('Hello')); // Latin
console.log(detectLanguage('你好')); // Han
console.log(detectLanguage('Привет')); // Cyrillic
// 密码强度检查
function checkPasswordStrength(password) {
const hasLetter = /\p{L}/v.test(password);
const hasNumber = /\p{N}/v.test(password);
const hasEmoji = /\p{Emoji}/v.test(password);
const hasHan = /\p{Script=Han}/v.test(password);
return {
hasLetter,
hasNumber,
hasEmoji,
hasHan,
isStrong: hasLetter && hasNumber && password.length >= 8
};
}
console.log(checkPasswordStrength('Password123'));
// { hasLetter: true, hasNumber: true, hasEmoji: false, hasHan: false, isStrong: true }
2. Array.fromAsync()
// 基本用法:从异步可迭代对象创建数组
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
const array = await Array.fromAsync(asyncGenerator());
console.log(array); // [1, 2, 3]
// 从异步 Map 创建数组
const asyncMap = new Map([
[Promise.resolve('key1'), Promise.resolve('value1')],
[Promise.resolve('key2'), Promise.resolve('value2')]
]);
const mapArray = await Array.fromAsync(asyncMap.entries());
console.log(mapArray); // [['key1', 'value1'], ['key2', 'value2']]
// 实际应用:异步数据处理
async function fetchUsers(ids) {
const userPromises = ids.map(id =>
fetch(`/api/users/${id}`).then(r => r.json())
);
const users = await Array.fromAsync(userPromises);
return users;
}
const users = await fetchUsers([1, 2, 3]);
console.log(users); // [{ id: 1, ... }, { id: 2, ... }, { id: 3, ... }]
// 分页数据收集
async function* fetchAllPages(url) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items;
hasMore = data.hasMore;
page++;
}
}
async function getAllItems(url) {
const allPages = await Array.fromAsync(fetchAllPages(url));
return allPages.flat();
}
// 文件处理
async function* readFiles(filePaths) {
for (const filePath of filePaths) {
const content = await readFile(filePath, 'utf8');
yield content;
}
}
async function concatenateFiles(filePaths) {
const contents = await Array.fromAsync(readFiles(filePaths));
return contents.join('\n');
}
// 异步过滤和映射
async function processAsyncData() {
const numbers = [1, 2, 3, 4, 5];
// 异步映射
const doubled = await Array.fromAsync(
numbers.map(async n => {
await new Promise(resolve => setTimeout(resolve, 100));
return n * 2;
})
);
console.log(doubled); // [2, 4, 6, 8, 10]
}
// 与其他异步方法的对比
async function compareAsyncMethods() {
const asyncIterable = (async function*() {
yield 1;
yield 2;
yield 3;
})();
// Array.fromAsync
const array1 = await Array.fromAsync(asyncIterable);
console.log('Array.fromAsync:', array1);
// 手动收集
const array2 = [];
for await (const item of asyncIterable) {
array2.push(item);
}
console.log('Manual collection:', array2);
// Promise.all (需要先转换为数组)
const promises = [];
for await (const item of asyncIterable) {
promises.push(Promise.resolve(item));
}
const array3 = await Promise.all(promises);
console.log('Promise.all:', array3);
}3. Object.groupBy() 和 Map.groupBy()
// Object.groupBy() - 按条件分组对象
const people = [
{ name: 'Alice', age: 25, department: 'Engineering' },
{ name: 'Bob', age: 30, department: 'Marketing' },
{ name: 'Charlie', age: 35, department: 'Engineering' },
{ name: 'Diana', age: 28, department: 'Marketing' }
];
const byDepartment = Object.groupBy(people, person => person.department);
console.log(byDepartment);
// {
// Engineering: [
// { name: 'Alice', age: 25, department: 'Engineering' },
// { name: 'Charlie', age: 35, department: 'Engineering' }
// ],
// Marketing: [
// { name: 'Bob', age: 30, department: 'Marketing' },
// { name: 'Diana', age: 28, department: 'Marketing' }
// ]
// }
// 按年龄分组
const byAgeGroup = Object.groupBy(people, person => {
if (person.age < 30) return 'Young';
return 'Senior';
});
console.log(byAgeGroup);
// {
// Young: [
// { name: 'Alice', age: 25, department: 'Engineering' },
// { name: 'Diana', age: 28, department: 'Marketing' }
// ],
// Senior: [
// { name: 'Bob', age: 30, department: 'Marketing' },
// { name: 'Charlie', age: 35, department: 'Engineering' }
// ]
// }
// Map.groupBy() - 返回 Map 而不是对象
const departmentMap = Map.groupBy(people, person => person.department);
console.log(departmentMap.get('Engineering'));
// [
// { name: 'Alice', age: 25, department: 'Engineering' },
// { name: 'Charlie', age: 35, department: 'Engineering' }
// ]
// 实际应用:数据处理
const products = [
{ id: 1, name: 'Laptop', price: 1000, category: 'Electronics' },
{ id: 2, name: 'Book', price: 20, category: 'Education' },
{ id: 3, name: 'Phone', price: 500, category: 'Electronics' },
{ id: 4, name: 'Course', price: 100, category: 'Education' }
];
// 按类别分组并计算统计信息
const categoryStats = Object.groupBy(products, product => product.category);
Object.keys(categoryStats).forEach(category => {
const items = categoryStats[category];
const totalValue = items.reduce((sum, item) => sum + item.price, 0);
console.log(`${category}: ${items.length} items, total value: $${totalValue}`);
});
// 复杂分组逻辑
const transactions = [
{ id: 1, amount: 100, type: 'income', date: '2023-01-01' },
{ id: 2, amount: -50, type: 'expense', date: '2023-01-02' },
{ id: 3, amount: 200, type: 'income', date: '2023-01-03' },
{ id: 4, amount: -75, type: 'expense', date: '2023-01-04' }
];
// 按月份和类型分组
const monthlyTransactions = Object.groupBy(transactions, transaction => {
const month = transaction.date.substring(0, 7); // '2023-01'
return `${month}-${transaction.type}`;
});
console.log(monthlyTransactions);
// {
// '2023-01-income': [
// { id: 1, amount: 100, type: 'income', date: '2023-01-01' },
// { id: 3, amount: 200, type: 'income', date: '2023-01-03' }
// ],
// '2023-01-expense': [
// { id: 2, amount: -50, type: 'expense', date: '2023-01-02' },
// { id: 4, amount: -75, type: 'expense', date: '2023-01-04' }
// ]
// }
🛠️ 在项目中使用与检测 ES 版本
1. TypeScript 配置
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020", // 编译目标
"module": "ESNext", // 模块系统
"lib": ["ES2020", "DOM"], // 使用的库
"moduleResolution": "node", // 模块解析
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}2. Babel 配置
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
browsers: ['> 1%', 'last 2 versions', 'not dead']
},
useBuiltIns: 'usage',
corejs: 3
}]
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-nullish-coalescing-operator'
]
};3. Vite 配置
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: {
target: 'es2020', // 构建目标
polyfillDynamicImport: true
},
define: {
__VERSION__: JSON.stringify(process.env.npm_package_version)
}
});4. 浏览器兼容性检查
// 检查 ES 特性支持
function checkESSupport() {
const features = {
'Arrow Functions': () => { try { eval('() => {}'); return true; } catch { return false; } },
'Async/Await': () => { try { eval('async function f(){}'); return true; } catch { return false; } },
'Optional Chaining': () => { try { eval('const obj = {}; obj?.prop'); return true; } catch { return false; } },
'Nullish Coalescing': () => { try { eval('const a = null ?? "default"'); return true; } catch { return false; } },
'Private Fields': () => { try { eval('class C { #field = 1; }'); return true; } catch { return false; } }
};
const results = {};
for (const [name, test] of Object.entries(features)) {
results[name] = test();
}
return results;
}
console.log('ES Features Support:', checkESSupport());📚 学习建议与路线
1. 基础阶段:ES6 核心特性
学习重点:
let/const与块级作用域- 箭头函数与 this 绑定
- 解构赋值与扩展运算符
- 模板字符串
- Promise 基础
- 类与模块化
实践项目:
- 构建待办事项应用
- 实现简单的数据可视化
- 创建个人博客系统
2. 进阶阶段:ES7-ES11 实用特性
学习重点:
async/await异步编程- 可选链与空值合并
- 动态导入与代码分割
- 数组方法的增强
- 对象操作的新方法
实践项目:
- 开发电商购物车
- 实现实时聊天应用
- 构建数据管理面板
3. 高级阶段:ES12-ES15 前沿特性
学习重点:
- 私有字段与类增强
- 顶层 await 与模块系统
- 正则表达式高级特性
- 异步数组操作
- 性能优化技巧
实践项目:
- 企业级应用架构
- 微前端系统
- 性能监控工具
4. 结合框架学习
React 生态:
// 使用现代 ES 语法
const UserProfile = ({ user, onUpdate }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleUpdate = async (updates) => {
try {
setLoading(true);
const updatedUser = await updateUser(user.id, updates);
onUpdate?.(updatedUser);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div className="user-profile">
<h2>{user?.name ?? 'Unknown User'}</h2>
{/* 组件内容 */}
</div>
);
};Vue 生态:
// Composition API + ES 特性
import { ref, computed, onMounted } from 'vue';
export default {
props: {
userId: {
type: String,
required: true
}
},
setup(props) {
const user = ref(null);
const loading = ref(false);
const error = ref(null);
const displayName = computed(() =>
user.value?.name ?? 'Loading...'
);
const loadUser = async () => {
try {
loading.value = true;
const response = await fetch(`/api/users/${props.userId}`);
user.value = await response.json();
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
onMounted(loadUser);
return {
user,
loading,
error,
displayName,
loadUser
};
}
};🎯 最佳实践
1. 代码风格
// 使用现代 ES 语法
const api = {
// 使用可选链和空值合并
async getUser(id) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return data?.user ?? null;
},
// 使用解构和默认参数
async updateUser(id, updates = {}) {
const { name, email, ...other } = updates;
const payload = {
...(name && { name }),
...(email && { email }),
...other
};
const response = await fetch(`/api/users/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return response.json();
}
};
// 使用私有字段
class UserService {
#cache = new Map();
#baseUrl;
constructor(baseUrl) {
this.#baseUrl = baseUrl;
}
async getUser(id) {
// 检查缓存
if (this.#cache.has(id)) {
return this.#cache.get(id);
}
// 获取数据
const user = await this.#fetchUser(id);
// 缓存结果
this.#cache.set(id, user);
return user;
}
async #fetchUser(id) {
const response = await fetch(`${this.#baseUrl}/users/${id}`);
return response.json();
}
}2. 错误处理
// 使用可选链和空值合并进行安全访问
function safeGetUser(data) {
return {
name: data?.user?.profile?.name ?? 'Unknown',
email: data?.user?.contact?.email ?? 'No email',
age: data?.user?.profile?.age ?? 0
};
}
// 使用 Promise.allSettled 处理批量操作
async function fetchMultipleData(urls) {
const results = await Promise.allSettled(
urls.map(url => fetch(url).then(r => r.json()))
);
return results.map((result, index) => ({
url: urls[index],
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason : null
}));
}
// 使用 Error Cause 进行错误链追踪
class APIError extends Error {
constructor(message, statusCode, cause) {
super(message, { cause });
this.name = 'APIError';
this.statusCode = statusCode;
}
}
async function apiCall(endpoint) {
try {
const response = await fetch(endpoint);
if (!response.ok) {
throw new APIError(
`API call failed: ${response.statusText}`,
response.status,
{ endpoint, status: response.status }
);
}
return await response.json();
} catch (error) {
if (error instanceof APIError) {
throw error;
}
throw new APIError(
'Network error occurred',
0,
{ endpoint, originalError: error }
);
}
}3. 性能优化
// 使用动态导入进行代码分割
class LazyLoader {
static modules = new Map();
static async load(moduleName) {
if (this.modules.has(moduleName)) {
return this.modules.get(moduleName);
}
try {
const module = await import(`./modules/${moduleName}.js`);
this.modules.set(moduleName, module);
return module;
} catch (error) {
console.error(`Failed to load module ${moduleName}:`, error);
throw error;
}
}
}
// 使用 Array.fromAsync 处理异步数据
async function processLargeDataset(data) {
const chunks = await Array.fromAsync(
createAsyncChunks(data, 1000) // 每次处理1000条
);
const results = [];
for (const chunk of chunks) {
const processed = await processChunk(chunk);
results.push(...processed);
}
return results;
}
async function* createAsyncChunks(array, size) {
for (let i = 0; i < array.length; i += size) {
yield array.slice(i, i + size);
}
}📖 相关资源
官方文档
学习工具
兼容性检查
结语
ECMAScript 的持续发展为 JavaScript 开发者带来了越来越强大的工具和更优雅的语法。从 ES6 的革命性变革到 ES15 的精细化改进,每个版本都在提升开发效率和代码质量。
掌握现代 JavaScript 不仅仅是学习新语法,更重要的是理解这些特性如何解决实际问题,如何提升代码的可读性、可维护性和性能。通过循序渐进的学习和实践,你将能够充分利用这些现代特性,编写出更加优雅、高效的 JavaScript 代码。
记住,学习是一个持续的过程。保持对新特性的关注,积极参与社区讨论,在实际项目中应用新技术,这些都是成为优秀 JavaScript 开发者的关键。开始你的现代 JavaScript 之旅吧!
WenHaoFree