当前位置: 首页 > ai >正文

ES6/ES2015 - ES16/ES2025

ES6/ES2015 - ES16/ES2025

ECMAScript(简称ES)是JavaScript的官方标准,从2015年开始每年发布一个新版本。

版本一览表

年份版本主要新特性
2015ES6/ES2015let/const、箭头函数、Class、模板字符串、解构赋值、模块、Promise
2016ES7/ES2016指数运算符、Array.includes()
2017ES8/ES2017async/await、Object.entries/values、padStart/padEnd
2018ES9/ES2018异步迭代、Promise.finally()、扩展运算符增强
2019ES10/ES2019Array.flat()、Object.fromEntries()、可选catch
2020ES11/ES2020BigInt、空值合并??、可选链?.
2021ES12/ES2021Promise.any()、replaceAll()、逻辑赋值操作符
2022ES13/ES2022私有字段、顶层await、Error.cause
2023ES14/ES2023数组不可变方法、Hashbang、Symbols做WeakMap键
2024ES15/ES2024分组API、Promise.withResolvers()、Temporal API
2025ES16/ES2025Iterator helpers、Promise.try()(提案中)

ES2015 (ES6) - JavaScript的重大革新

ES6是JavaScript历史上最重要的更新,引入了大量现代编程特性。

1. let 和 const - 块级作用域

问题背景: var存在变量提升和函数作用域问题。

// var 的问题
for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 输出三个3
}// let 解决方案
for (let i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 输出0, 1, 2
}// const 常量声明
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable// const 对象可以修改内容
const user = { name: 'Alice' };
user.name = 'Bob'; // 允许
// user = {}; // 错误:不能重新赋值

2. 箭头函数 - 简洁的函数语法

// 传统函数
function add(a, b) {return a + b;
}// 箭头函数
const add = (a, b) => a + b;// 单参数可省略括号
const square = x => x * x;// 多行函数体需要大括号和return
const greet = name => {const message = `Hello, ${name}!`;return message.toUpperCase();
};// this绑定的区别
class Timer {constructor() {this.seconds = 0;}// 传统函数:this指向调用者start1() {setInterval(function() {this.seconds++; // this指向window/global}, 1000);}// 箭头函数:this绑定到定义时的上下文start2() {setInterval(() => {this.seconds++; // this指向Timer实例}, 1000);}
}

3. Class类 - 面向对象编程

// ES6类语法
class Animal {// 构造函数constructor(name, species) {this.name = name;this.species = species;}// 实例方法speak() {return `${this.name} makes a sound`;}// 静态方法static getSpeciesCount() {return Animal.count || 0;}
}// 继承
class Dog extends Animal {constructor(name, breed) {super(name, 'Canine'); // 调用父类构造函数this.breed = breed;}// 重写父类方法speak() {return `${this.name} barks`;}// 新方法wagTail() {return `${this.name} wags tail happily`;}
}const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.speak()); // "Buddy barks"
console.log(myDog.wagTail()); // "Buddy wags tail happily"

4. 模板字符串 - 字符串插值

const name = 'Alice';
const age = 30;// 传统字符串拼接
const oldWay = 'Hello, my name is ' + name + ' and I am ' + age + ' years old.';// 模板字符串
const newWay = `Hello, my name is ${name} and I am ${age} years old.`;// 多行字符串
const multiLine = `这是第一行这是第二行当前时间:${new Date().toISOString()}
`;// 表达式和函数调用
const price = 99.99;
const message = `商品价格:$${price.toFixed(2)},折扣后:$${(price * 0.8).toFixed(2)}`;// 标签模板
function highlight(strings, ...values) {return strings.reduce((result, string, i) => {return result + string + (values[i] ? `<mark>${values[i]}</mark>` : '');}, '');
}const product = 'iPhone';
const count = 3;
const html = highlight`您有 ${count}${product} 在购物车中`;
// "您有 <mark>3</mark> 个 <mark>iPhone</mark> 在购物车中"

5. 解构赋值 - 提取数据的便捷方式

// 数组解构
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;
console.log(first); // 'red'// 跳过元素
const [, , blue] = colors;
console.log(blue); // 'blue'// 剩余元素
const [primary, ...secondary] = colors;
console.log(secondary); // ['green', 'blue']// 对象解构
const person = {name: 'Alice',age: 30,city: 'New York',country: 'USA'
};const { name, age } = person;
console.log(name, age); // 'Alice' 30// 重命名变量
const { name: userName, city: location } = person;
console.log(userName, location); // 'Alice' 'New York'// 默认值
const { height = 170 } = person;
console.log(height); // 170// 嵌套解构
const user = {id: 1,profile: {name: 'Bob',settings: {theme: 'dark'}}
};const { profile: { name: profileName, settings: { theme } } } = user;
console.log(profileName, theme); // 'Bob' 'dark'// 函数参数解构
function greetUser({ name, age = 18 }) {return `Hello ${name}, you are ${age} years old`;
}greetUser({ name: 'Charlie', age: 25 }); // "Hello Charlie, you are 25 years old"

6. 模块系统 - import/export

// math.js - 导出模块
export const PI = 3.14159;export function add(a, b) {return a + b;
}export function multiply(a, b) {return a * b;
}// 默认导出
export default function divide(a, b) {return a / b;
}// main.js - 导入模块
import divide, { PI, add, multiply } from './math.js';console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
console.log(divide(10, 2)); // 5// 重命名导入
import { add as sum } from './math.js';// 导入全部
import * as MathUtils from './math.js';
console.log(MathUtils.add(1, 2)); // 3

7. Promise - 异步编程

// 创建Promise
const fetchData = () => {return new Promise((resolve, reject) => {setTimeout(() => {const success = Math.random() > 0.5;if (success) {resolve({ data: 'Hello World' });} else {reject(new Error('获取数据失败'));}}, 1000);});
};// 使用Promise
fetchData().then(result => {console.log('成功:', result.data);return result.data.toUpperCase();}).then(upperData => {console.log('转换后:', upperData);}).catch(error => {console.log('错误:', error.message);}).finally(() => {console.log('请求完成');});// Promise链式调用
Promise.resolve(1).then(x => x + 1).then(x => x * 2).then(x => console.log(x)); // 4// Promise.all - 并行执行
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);Promise.all([promise1, promise2, promise3]).then(values => {console.log(values); // [1, 2, 3]});// Promise.race - 竞态
const slow = new Promise(resolve => setTimeout(() => resolve('慢'), 1000));
const fast = new Promise(resolve => setTimeout(() => resolve('快'), 100));Promise.race([slow, fast]).then(result => {console.log(result); // '快'});

ES2016 (ES7) - 小而精的更新

1. 指数运算符 (**)

// 传统方式
Math.pow(2, 3); // 8// ES2016 指数运算符
2 ** 3; // 8
2 ** 0.5; // 1.4142135623730951 (平方根)// 支持复合赋值
let x = 2;
x **= 3; // 相当于 x = x ** 3
console.log(x); // 8// 优先级高于一元运算符
-2 ** 2; // -4 (相当于 -(2 ** 2))
(-2) ** 2; // 4

2. Array.prototype.includes()

const fruits = ['apple', 'banana', 'orange'];// ES2016之前
fruits.indexOf('banana') !== -1; // true
fruits.indexOf('grape') !== -1; // false// ES2016 includes方法
fruits.includes('banana'); // true
fruits.includes('grape'); // false// 处理NaN的区别
const numbers = [1, 2, NaN, 4];
numbers.indexOf(NaN); // -1 (找不到NaN)
numbers.includes(NaN); // true (正确找到NaN)// 从指定位置开始查找
const letters = ['a', 'b', 'c', 'a'];
letters.includes('a', 2); // false (从索引2开始找,没找到'a')
letters.includes('a', 3); // true (从索引3开始找,找到了'a')

ES2017 (ES8) - 异步编程的革命

1. async/await - 异步代码同步写法

// Promise方式
function fetchUserData() {return fetch('/api/user').then(response => response.json()).then(user => {console.log('用户:', user);return fetch(`/api/posts/${user.id}`);}).then(response => response.json()).then(posts => {console.log('帖子:', posts);return posts;}).catch(error => {console.log('错误:', error);});
}// async/await方式
async function fetchUserDataAsync() {try {const userResponse = await fetch('/api/user');const user = await userResponse.json();console.log('用户:', user);const postsResponse = await fetch(`/api/posts/${user.id}`);const posts = await postsResponse.json();console.log('帖子:', posts);return posts;} catch (error) {console.log('错误:', error);}
}// 并行执行多个异步操作
async function fetchMultipleData() {try {// 并行执行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 };} catch (error) {console.log('获取数据失败:', error);}
}// 循环中使用await
async function processItems(items) {// 串行处理for (const item of items) {await processItem(item);}// 并行处理await Promise.all(items.map(item => processItem(item)));
}

2. Object.entries() 和 Object.values()

const person = {name: 'Alice',age: 30,city: 'New York'
};// Object.keys() (ES5已有)
Object.keys(person); // ['name', 'age', 'city']// Object.values() (ES2017新增)
Object.values(person); // ['Alice', 30, 'New York']// Object.entries() (ES2017新增)
Object.entries(person); // [['name', 'Alice'], ['age', 30], ['city', 'New York']]// 实际应用场景
// 遍历对象
for (const [key, value] of Object.entries(person)) {console.log(`${key}: ${value}`);
}// 对象转Map
const personMap = new Map(Object.entries(person));// 过滤对象属性
const filteredEntries = Object.entries(person).filter(([key, value]) => typeof value === 'string');
const filteredObject = Object.fromEntries(filteredEntries);
console.log(filteredObject); // { name: 'Alice', city: 'New York' }// 计算对象属性数量
const counts = { apple: 5, banana: 3, orange: 8 };
const total = Object.values(counts).reduce((sum, count) => sum + count, 0);
console.log(total); // 16

3. String.prototype.padStart() 和 padEnd()

// padStart - 在开头填充
'5'.padStart(3, '0'); // '005'
'hello'.padStart(10, '*'); // '*****hello'// padEnd - 在结尾填充
'5'.padEnd(3, '0'); // '500'
'hello'.padEnd(10, '*'); // 'hello*****'// 实际应用场景
// 格式化数字
const formatNumber = (num, length = 4) => {return String(num).padStart(length, '0');
};formatNumber(5); // '0005'
formatNumber(123); // '0123'// 格式化时间
const formatTime = (hours, minutes, seconds) => {const h = String(hours).padStart(2, '0');const m = String(minutes).padStart(2, '0');const s = String(seconds).padStart(2, '0');return `${h}:${m}:${s}`;
};formatTime(9, 5, 3); // '09:05:03'// 创建简单的表格对齐
const data = [{ name: 'Alice', score: 95 },{ name: 'Bob', score: 87 },{ name: 'Charlie', score: 92 }
];data.forEach(({ name, score }) => {const formattedName = name.padEnd(10, ' ');const formattedScore = String(score).padStart(3, ' ');console.log(`${formattedName} ${formattedScore}`);
});
// Alice       95
// Bob         87
// Charlie     92

ES2018 (ES9) - 异步迭代和增强功能

1. 异步迭代 (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 (依次输出)}
}// 处理异步数据流
async function fetchUserPosts(userIds) {async function* postGenerator() {for (const userId of userIds) {const response = await fetch(`/api/users/${userId}/posts`);const posts = await response.json();yield posts;}}for await (const posts of postGenerator()) {console.log('处理用户帖子:', posts);}
}// 读取文件流 (Node.js环境)
const fs = require('fs');async function readLargeFile() {const stream = fs.createReadStream('large-file.txt', { encoding: 'utf8' });for await (const chunk of stream) {console.log('读取到数据块:', chunk.length);// 处理数据块}
}

2. Promise.prototype.finally()

let isLoading = true;fetch('/api/data').then(response => {if (!response.ok) {throw new Error('网络请求失败');}return response.json();}).then(data => {console.log('数据获取成功:', data);// 处理成功逻辑}).catch(error => {console.error('请求失败:', error);// 处理错误逻辑}).finally(() => {isLoading = false; // 无论成功失败都会执行console.log('请求完成,隐藏加载状态');});// 实际应用:资源清理
async function processFile(filename) {let fileHandle;try {fileHandle = await openFile(filename);const data = await fileHandle.read();return processData(data);} catch (error) {console.error('文件处理失败:', error);throw error;} finally {// 确保文件句柄被关闭if (fileHandle) {await fileHandle.close();}}
}

3. 对象扩展运算符

// 对象扩展运算符
const baseConfig = {host: 'localhost',port: 3000,debug: false
};const devConfig = {...baseConfig,debug: true,hot: true
};console.log(devConfig);
// {
//   host: 'localhost',
//   port: 3000,
//   debug: true,
//   hot: true
// }// 合并多个对象
const user = { name: 'Alice', age: 30 };
const preferences = { theme: 'dark', language: 'en' };
const permissions = { admin: false, editor: true };const fullProfile = { ...user, ...preferences, ...permissions };// 对象的浅拷贝
const originalUser = { name: 'Bob', hobbies: ['reading', 'swimming'] };
const clonedUser = { ...originalUser };// 注意:扩展运算符是浅拷贝
clonedUser.hobbies.push('cooking'); // 会影响原对象的hobbies数组// 深拷贝需要其他方法
const deepClone = JSON.parse(JSON.stringify(originalUser));// 函数参数中的对象扩展
function createUser(defaults, overrides) {return {id: Math.random(),createdAt: new Date(),...defaults,...overrides};
}const newUser = createUser({ name: 'Unknown', role: 'user' },{ name: 'Charlie', email: 'charlie@example.com' }
);

4. 正则表达式增强

// 命名捕获组
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2023-12-25'.match(dateRegex);console.log(match.groups.year);  // '2023'
console.log(match.groups.month); // '12'
console.log(match.groups.day);   // '25'// replace中使用命名捕获组
const formatted = '2023-12-25'.replace(dateRegex, '$<day>/$<month>/$<year>');
console.log(formatted); // '25/12/2023'// s 标志 (dotAll) - 让.匹配任何字符包括换行符
const multilineText = `第一行
第二行
第三行`;const regex1 = /第一行.第二行/; // 不匹配,因为.不匹配换行符
const regex2 = /第一行.第二行/s; // 匹配,s标志让.匹配换行符console.log(regex1.test(multilineText)); // false
console.log(regex2.test(multilineText)); // true// 正向否定断言和正向肯定断言
const password = 'myPassword123';// 正向肯定断言:必须包含数字
const hasNumber = /(?=.*\d)/;
console.log(hasNumber.test(password)); // true// 正向否定断言:不能包含特殊字符
const noSpecialChar = /^(?!.*[!@#$%^&*]).*$/;
console.log(noSpecialChar.test(password)); // true

ES2019 (ES10) - 数组和对象处理增强

1. Array.prototype.flat() 和 flatMap()

// 数组扁平化
const nestedArray = [1, [2, 3], [4, [5, 6]]];// flat() - 默认深度为1
nestedArray.flat(); // [1, 2, 3, 4, [5, 6]]// 指定扁平化深度
nestedArray.flat(2); // [1, 2, 3, 4, 5, 6]// 完全扁平化
const deepNested = [1, [2, [3, [4, [5]]]]];
deepNested.flat(Infinity); // [1, 2, 3, 4, 5]// flatMap() = map + flat
const sentences = ['Hello world', 'How are you', 'Nice day'];// 传统方式
sentences.map(s => s.split(' ')).flat();
// [['Hello', 'world'], ['How', 'are', 'you'], ['Nice', 'day']] -> 
// ['Hello', 'world', 'How', 'are', 'you', 'Nice', 'day']// flatMap方式
sentences.flatMap(s => s.split(' '));
// ['Hello', 'world', 'How', 'are', 'you', 'Nice', 'day']// 实际应用:处理用户评论
const users = [{ name: 'Alice', comments: ['Great post!', 'Thanks for sharing'] },{ name: 'Bob', comments: ['Interesting', 'I agree'] },{ name: 'Charlie', comments: ['Nice work'] }
];// 获取所有评论
const allComments = users.flatMap(user => user.comments);
console.log(allComments);
// ['Great post!', 'Thanks for sharing', 'Interesting', 'I agree', 'Nice work']// 复杂的flatMap用例:数据处理管道
const orders = [{ id: 1, items: [{ name: 'laptop', price: 1000 }, { name: 'mouse', price: 25 }] },{ id: 2, items: [{ name: 'keyboard', price: 100 }] }
];const expensiveItems = orders.flatMap(order => order.items).filter(item => item.price > 50).map(item => item.name);console.log(expensiveItems); // ['laptop', 'keyboard']

2. Object.fromEntries()

// 将键值对数组转换为对象
const entries = [['name', 'Alice'], ['age', 30], ['city', 'New York']];
const person = Object.fromEntries(entries);
console.log(person); // { name: 'Alice', age: 30, city: 'New York' }// 与Object.entries()配合使用
const originalObj = { a: 1, b: 2, c: 3 };// 对对象的值进行转换
const doubledObj = Object.fromEntries(Object.entries(originalObj).map(([key, value]) => [key, value * 2])
);
console.log(doubledObj); // { a: 2, b: 4, c: 6 }// 过滤对象属性
const user = {name: 'Bob',age: 25,email: 'bob@example.com',password: 'secret123',isAdmin: false
};// 创建公开用户信息(移除敏感信息)
const publicUser = Object.fromEntries(Object.entries(user).filter(([key]) => key !== 'password')
);// Map转对象
const userMap = new Map([['id', 123],['name', 'Charlie'],['email', 'charlie@example.com']
]);const userObj = Object.fromEntries(userMap);
console.log(userObj); // { id: 123, name: 'Charlie', email: 'charlie@example.com' }// 查询参数处理
const searchParams = new URLSearchParams('?name=Alice&age=30&city=NYC');
const paramsObj = Object.fromEntries(searchParams);
console.log(paramsObj); // { name: 'Alice', age: '30', city: 'NYC' }

3. String.prototype.trimStart() 和 trimEnd()

const messyString = '   Hello World   ';// ES5方法
messyString.trim(); // 'Hello World' (移除两端空格)// ES2019新方法
messyString.trimStart(); // 'Hello World   ' (只移除开头空格)
messyString.trimEnd();   // '   Hello World' (只移除结尾空格)// 别名方法(为了兼容性)
messyString.trimLeft();  // 等同于trimStart()
messyString.trimRight(); // 等同于trimEnd()// 实际应用:处理用户输入
function processUserInput(input) {// 移除开头空格,但保留结尾的换行符或空格(可能有格式意义)return input.trimStart();
}// 处理代码格式化
const codeLines = ['    function example() {','        console.log("hello");','    }'
];// 移除统一的缩进
const minIndent = Math.min(...codeLines.filter(line => line.trim()).map(line => line.length - line.trimStart().length)
);const formattedCode = codeLines.map(line => line.trimStart().padStart(line.trimStart().length + Math.max(0, line.length - line.trimStart().length - minIndent), ' ')
);

4. 可选的 catch 参数

// ES2019之前:必须提供catch参数
try {JSON.parse(invalidJson);
} catch (error) {console.log('解析失败'); // 即使不使用error参数也必须声明
}// ES2019:catch参数可选
try {JSON.parse(invalidJson);
} catch {console.log('解析失败'); // 不需要error参数
}// 实际应用:功能检测
let supportsLocalStorage = false;try {localStorage.setItem('test', 'test');localStorage.removeItem('test');supportsLocalStorage = true;
} catch {// 忽略错误,仅用于检测是否支持localStoragesupportsLocalStorage = false;
}// 另一个场景:忽略特定错误
function loadConfig() {try {// 尝试读取本地配置return JSON.parse(localStorage.getItem('appConfig'));} catch {// 配置读取失败或解析错误时,返回默认配置return { theme: 'light', fontSize: 16 };}
}

5. Function.prototype.toString() 增强

// ES2019之前:toString()会省略注释和空格
function add(a, b) {// 这是加法函数return a + b;
}// ES2019之前的输出(可能):"function add(a, b) { return a + b; }"
// ES2019的输出:保留原始格式
console.log(add.toString());
// 输出:
// function add(a, b) {
//   // 这是加法函数
//   return a + b;
// }// 箭头函数也支持
const multiply = (a, b) => {/* 乘法函数 */return a * b;
};console.log(multiply.toString());
// 输出:
// (a, b) => {
//   /* 乘法函数 */
//   return a * b;
// }

ES2020 (ES11) - 解决实际开发痛点

1. BigInt - 任意精度整数

// 传统Number的限制(2^53 - 1)
const maxSafeInt = Number.MAX_SAFE_INTEGER;
console.log(maxSafeInt); // 9007199254740991// 超出安全整数范围的计算会失真
console.log(maxSafeInt + 1); // 9007199254740992
console.log(maxSafeInt + 2); // 9007199254740992(错误结果)// BigInt解决方案
const bigInt1 = 9007199254740991n; // 后缀n表示BigInt
const bigInt2 = BigInt(9007199254740991); // 构造函数方式// 正确的大整数计算
console.log(bigInt1 + 1n); // 9007199254740992n
console.log(bigInt1 + 2n); // 9007199254740993n// 大整数比较(需类型一致)
console.log(10n === 10); // false(类型不同)
console.log(10n === BigInt(10)); // true// 实际应用:处理超大ID或金额
const transactionId = 123456789012345678901234567890n;
const largeAmount = 999999999999999999999999999n;// 注意:BigInt不能与Number直接运算
// console.log(10n + 5); // TypeError
console.log(10n + BigInt(5)); // 15n
console.log(Number(10n) + 5); // 15(需确保值在安全范围内)

2. 空值合并运算符 (??)

// 传统逻辑运算符的问题(0、''、false会被误判)
const count = 0;
const displayCount = count || '未知'; // '未知'(错误,0是有效数值)const username = '';
const displayName = username || '匿名用户'; // '匿名用户'(错误,空字符串是有效名称)// 空值合并运算符:仅当左侧为null/undefined时才使用右侧值
const displayCount2 = count ?? '未知'; // 0(正确)
const displayName2 = username ?? '匿名用户'; // ''(正确)const age = null;
const displayAge = age ?? 18; // 18(正确,null使用默认值)const height = undefined;
const displayHeight = height ?? 170; // 170(正确,undefined使用默认值)// 实际应用:配置项默认值
function createConfig(options) {return {theme: options.theme ?? 'light',fontSize: options.fontSize ?? 16,showSidebar: options.showSidebar ?? true // false会被保留};
}// 与可选链配合使用
const user = {name: 'Alice',address: {city: 'New York'// zipCode未定义}
};const zipCode = user.address.zipCode ?? '未填写';
console.log(zipCode); // '未填写'

3. 可选链运算符 (?.)

// 传统嵌套属性访问的问题
const user = {name: 'Alice',address: {city: 'New York'// street未定义}
};// 传统方式:需要层层判断
const street = user.address && user.address.street && user.address.street.name;
console.log(street); // undefined(无错误,但代码繁琐)// 可选链方式:简洁安全
const street2 = user.address?.street?.name;
console.log(street2); // undefined(无错误,代码简洁)// 数组元素访问
const users = [{ name: 'Bob', age: 25 },// 第二个元素未定义{ name: 'Charlie', age: 30 }
];const secondUserName = users[1]?.name;
console.log(secondUserName); // undefined(无错误)const fourthUserName = users[3]?.name;
console.log(fourthUserName); // undefined(无错误)// 函数调用安全
const utils = {calculate: (a, b) => a + b// format未定义
};// 传统方式:需判断函数是否存在
const result1 = utils.format && utils.format('hello');// 可选链方式
const result2 = utils.format?.('hello');
console.log(result2); // undefined(无错误)// 实际应用:API数据处理
async function fetchUserName(userId) {const response = await fetch(`/api/users/${userId}`);const data = await response.json();// 安全获取嵌套属性,无需担心数据结构变化return data?.user?.profile?.name ?? '未知用户';
}

4. String.prototype.matchAll()

const text = 'Hello 123, World 456! Welcome 789';
const regex = /\d+/g; // 匹配所有数字// 传统方式:多次调用exec()
let match;
const numbers1 = [];
while ((match = regex.exec(text)) !== null) {numbers1.push(match[0]);
}
console.log(numbers1); // ['123', '456', '789']// ES2020 matchAll():返回迭代器
const matches = text.matchAll(regex);// 转换为数组
const numbers2 = [...matches];
console.log(numbers2); 
// [
//   ['123', index: 6, input: 'Hello 123, World 456! Welcome 789', groups: undefined],
//   ['456', index: 15, input: 'Hello 123, World 456! Welcome 789', groups: undefined],
//   ['789', index: 28, input: 'Hello 123, World 456! Welcome 789', groups: undefined]
// ]// 配合for...of循环
const numbers3 = [];
for (const match of text.matchAll(regex)) {numbers3.push(match[0]);
}// 命名捕获组场景
const dateText = '今天是2023-10-05,明天是2023-10-06';
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;const dates = [];
for (const match of dateText.matchAll(dateRegex)) {dates.push({year: match.groups.year,month: match.groups.month,day: match.groups.day});
}console.log(dates);
// [
//   { year: '2023', month: '10', day: '05' },
//   { year: '2023', month: '10', day: '06' }
// ]

5. 动态导入 (import())

// 传统静态导入:无论是否需要都会加载
import { heavyFunction } from './heavy-module.js';// ES2020动态导入:按需加载
async function loadHeavyModule() {try {// 动态导入返回Promiseconst heavyModule = await import('./heavy-module.js');heavyModule.heavyFunction(); // 调用模块中的函数// 解构导入const { utilityFunction } = await import('./utils.js');utilityFunction();} catch (error) {console.error('模块加载失败:', error);}
}// 实际应用:路由懒加载(React/Vue场景)
// React示例
function Home() {return <div>首页</div>;
}// 懒加载其他路由组件
const About = React.lazy(() => import('./About.js'));
const Contact = React.lazy(() => import('./Contact.js'));function App() {return (<Router><Suspense fallback={<div>加载中...</div>}><Routes><Route path="/" element={<Home />} /><Route path="/about" element={<About />} /> {/* 按需加载 */}<Route path="/contact" element={<Contact />} /> {/* 按需加载 */}</Routes></Suspense></Router>);
}// 条件加载
async function loadFeatureModule(feature) {switch (feature) {case 'chart':return import('./chart-module.js');case 'editor':return import('./editor-module.js');default:throw new Error('未知功能模块');}
}

6. 顶层 await (Top-level await)

// ES2020之前:await只能在async函数中使用
// 模块顶层使用await会报错// ES2020:模块顶层支持await
// config.js
const fetchConfig = async () => {const response = await fetch('/api/config');return response.json();
};// 顶层await:模块加载会等待配置获取完成
export const config = await fetchConfig();// main.js
import { config } from './config.js';// 导入时config已准备就绪,无需额外等待
console.log('应用配置:', config);
document.title = config.appName;// 实际应用:依赖初始化
// db.js
import { initDatabase } from './database.js';// 顶层await初始化数据库连接
export const db = await initDatabase({host: 'localhost',port: 5432,name: 'appdb'
});// 使用时db已连接
import { db } from './db.js';async function getUser(id) {return db.query('SELECT * FROM users WHERE id = ?', [id]);
}// 注意:顶层await会阻塞模块执行,适用于必要的初始化场景
// 避免在不需要等待的场景滥用

ES2021 (ES12) - 提升开发效率

1. Promise.any()

// Promise.race() vs Promise.any()
// Promise.race(): 只要有一个Promise settle(成功/失败)就返回
// Promise.any(): 只要有一个Promise成功就返回,全部失败才返回失败// 示例:多个API请求,取第一个成功的结果
const fetchFromServer1 = () => fetch('/api/data1');
const fetchFromServer2 = () => fetch('/api/data2');
const fetchFromServer3 = () => fetch('/api/data3');// 使用Promise.any()
Promise.any([fetchFromServer1(), fetchFromServer2(), fetchFromServer3()]).then(response => {console.log('第一个成功的响应:', response);return response.json();}).then(data => console.log('获取到的数据:', data)).catch(error => {console.error('所有请求都失败了:', error);console.log('失败原因数组:', error.errors); // AggregateError包含所有失败原因});// 实际应用:图片加载容错
function loadImage(url) {return new Promise((resolve, reject) => {const img = new Image();img.src = url;img.onload = () => resolve(img);img.onerror = () => reject(new Error(`加载图片失败: ${url}`));});
}// 尝试从多个CDN加载图片,取第一个成功的
const imageUrls = ['https://cdn1.example.com/image.jpg','https://cdn2.example.com/image.jpg','https://cdn3.example.com/image.jpg'
];Promise.any(imageUrls.map(url => loadImage(url))).then(img => {document.body.appendChild(img);}).catch(error => {console.error('所有图片加载失败:', error);// 显示默认图片const defaultImg = new Image();defaultImg.src = '/images/default.jpg';document.body.appendChild(defaultImg);});

2. String.prototype.replaceAll()

const text = 'Hello World! World is beautiful. I love World.';// ES2021之前:替换所有匹配需要使用正则表达式g标志
const newText1 = text.replace(/World/g, 'Earth');
console.log(newText1); 
// 'Hello Earth! Earth is beautiful. I love Earth.'// ES2021 replaceAll(): 无需正则,直接替换所有匹配
const newText2 = text.replaceAll('World', 'Earth');
console.log(newText2); 
// 'Hello Earth! Earth is beautiful. I love Earth.'// 处理特殊字符(无需转义)
const url = 'https://example.com/path?param1=value1&param2=value2&param1=value3';// 替换所有param1为newParam
const newUrl = url.replaceAll('param1', 'newParam');
console.log(newUrl); 
// 'https://example.com/path?newParam=value1&param2=value2&newParam=value3'// 与正则表达式配合(需g标志,否则报错)
const messyText = 'apple, Apple, APPLE';// 错误:未使用g标志
// messyText.replaceAll(/apple/i, 'orange'); // TypeError// 正确:使用g标志
const cleanText = messyText.replaceAll(/apple/gi, 'orange');
console.log(cleanText); // 'orange, orange, orange'// 实际应用:敏感信息替换
function redactSensitiveData(data, sensitiveKeys) {let jsonStr = JSON.stringify(data);sensitiveKeys.forEach(key => {// 匹配 "key":"value" 格式中的valueconst regex = new RegExp(`"${key}":"[^"]+"`, 'g');jsonStr = jsonStr.replaceAll(regex, `"${key}":"[REDACTED]"`);});return JSON.parse(jsonStr);
}const userData = {name: 'Alice',email: 'alice@example.com',password: 'secret123',phone: '1234567890'
};const redactedData = redactSensitiveData(userData, ['password', 'phone']);
console.log(redactedData);
// {
//   name: 'Alice',
//   email: 'alice@example.com',
//   password: '[REDACTED]',
//   phone: '[REDACTED]'
// }

3. 逻辑赋值操作符 (&&=, ||=, ??=)

// 1. 逻辑与赋值 (&&=)
// 语法:a &&= b → 等同于 a = a && b
// 只有a为真时,才将b赋值给alet x = 5;
x &&= 10; // x = 5 && 10 → 10(5为真,赋值10)
console.log(x); // 10let y = null;
y &&= 10; // y = null && 10 → null(null为假,保持原值)
console.log(y); // null// 实际应用:安全更新对象属性
let user = { name: 'Alice', age: 30 };
user.address &&= { ...user.address, city: 'New York' };
// 等同于:if (user.address) user.address = { ...user.address, city: 'New York' }// 2. 逻辑或赋值 (||=)
// 语法:a ||= b → 等同于 a = a || b
// 只有a为假时,才将b赋值给alet count = 0;
count ||= 10; // count = 0 || 10 → 10(0为假,赋值10)
console.log(count); // 10let name = 'Bob';
name ||= 'Unknown'; // name = 'Bob' || 'Unknown' → 'Bob'(真,保持原值)
console.log(name); // 'Bob'// 3. 空值合并赋值 (??=)
// 语法:a ??= b → 等同于 a = a ?? b
// 只有a为null/undefined时,才将b赋值给a(解决||=误判0/''/false的问题)let score = 0;
score ??= 100; // score = 0 ?? 100 → 0(0不是null/undefined,保持原值)
console.log(score); // 0let username = '';
username ??= 'Guest'; // username = '' ?? 'Guest' → ''(保持原值)
console.log(username); // ''let age = null;
age ??= 18; // age = null ?? 18 → 18(null,赋值18)
console.log(age); // 18// 实际应用:设置默认配置
function setupOptions(options) {// 仅当options.theme为null/undefined时设置默认值options.theme ??= 'light';// 仅当options.fontSize为null/undefined时设置默认值options.fontSize ??= 16;// 仅当options.showSidebar为null/undefined时设置默认值options.showSidebar ??= true;return options;
}const userOptions = {theme: 'dark',fontSize: 0, // 0是有效配置,不会被覆盖showSidebar: false // false是有效配置,不会被覆盖
};const finalOptions = setupOptions(userOptions);
console.log(finalOptions);
// { theme: 'dark', fontSize: 0, showSidebar: false }

4. 数字分隔符 (Numeric Separators)

// 传统大数字:难以阅读
const population = 7800000000; // 78亿,不易快速识别
const budget = 1234567890123; // 1.2万亿,阅读困难// ES2021数字分隔符:使用_分隔数字,增强可读性
const population2 = 7_800_000_000; // 78亿,清晰可见
const budget2 = 1_234_567_890_123; // 1.2万亿,易于识别console.log(population2); // 7800000000(输出时自动忽略_)
console.log(budget2 === 1234567890123); // true// 小数也支持
const pi = 3.141_592_653_5;
const price = 999.99;
const discount = 0.00_5; // 0.005// 二进制、八进制、十六进制也支持
const binary = 0b1010_1100_1011; // 二进制
const octal = 0o123_456_700; // 八进制
const hex = 0x1a_bc_3d_ef; // 十六进制// 实际应用:财务数据
const salary = 125_000; // 12.5万
const tax = 28_750; // 2.875万
const netIncome = salary - tax; // 96250// 科学计数法
const avogadroNumber = 6.022_140_76e23; // 阿伏伽德罗常数// 注意事项:
// 1. 不能在数字开头或结尾
// const invalid1 = _123; // 错误
// const invalid2 = 123_; // 错误// 2. 不能在小数点前后
// const invalid3 = 123_.45; // 错误
// const invalid4 = 123._45; // 错误// 3. 不能在科学计数法的e前后
// const invalid5 = 123e_45; // 错误
// const invalid6 = 123_e45; // 错误

5. WeakRef 和 FinalizationRegistry

// WeakRef:创建对象的弱引用,不阻止垃圾回收
let obj = { data: '重要数据' };
const weakRef = new WeakRef(obj);// 获取弱引用指向的对象
console.log(weakRef.deref()); // { data: '重要数据' }// 释放强引用
obj = null;// 垃圾回收后,deref()返回undefined(时机不确定)
// 注意:垃圾回收行为在不同环境下可能不同
setTimeout(() => {console.log(weakRef.deref()); // 可能为undefined
}, 1000);// FinalizationRegistry:对象被垃圾回收后执行回调
const registry = new FinalizationRegistry((value) => {console.log(`对象被回收了,附加数据:${value}`);
});// 注册对象:当obj被垃圾回收时,调用回调并传入附加数据
let targetObj = { id: 1 };
registry.register(targetObj, 'targetObj的附加信息', targetObj);// 释放强引用
targetObj = null;// 垃圾回收后,会触发FinalizationRegistry的回调
// 输出:"对象被回收了,附加数据:targetObj的附加信息"// 实际应用:缓存管理
class WeakCache {constructor() {this.cache = new Map();this.registry = new FinalizationRegistry(key => {this.cache.delete(key);console.log(`缓存项 ${key} 已清理`);});}set(key, value) {this.cache.set(key, value);// 注册:当value被垃圾回收时,删除对应的缓存项this.registry.register(value, key, value);}get(key) {return this.cache.get(key);}delete(key) {const value = this.cache.get(key);if (value) {this.registry.unregister(value); // 取消注册this.cache.delete(key);}}
}// 使用弱引用缓存
const cache = new WeakCache();let data = { id: 1, content: '大数据对象' };
cache.set('data1', data);console.log(cache.get('data1')); // { id: 1, content: '大数据对象' }// 释放强引用
data = null;// 当data被垃圾回收后,缓存项会自动清理
// 输出:"缓存项 data1 已清理"

ES2022 (ES13) - 增强安全性和模块化

1. 类的私有字段 (#)

// ES2022之前:模拟私有属性(通过命名约定或闭包,并非真正私有)
class User {constructor(name, password) {this.name = name;// 约定:下划线开头表示私有,但仍可外部访问this._password = password;}checkPassword(password) {return this._password === password;}
}const user1 = new User('Alice', 'secret123');
console.log(user1._password); // 'secret123'(可外部访问,不安全)// ES2022:真正的私有字段(#开头)
class SecureUser {// 私有字段声明(可选,也可在构造函数中直接定义)#password;#lastLogin;constructor(name, password) {this.name = name; // 公有字段this.#password = password; // 私有字段this.#lastLogin = new Date(); // 私有字段}checkPassword(password) {// 类内部可访问私有字段return this.#password === password;}getLastLogin() {// 提供访问私有字段的公有方法return this.#lastLogin.toISOString();}// 私有方法#updateLastLogin() {this.#lastLogin = new Date();}login(password) {if (this.checkPassword(password)) {this.#updateLastLogin(); // 内部调用私有方法return true;}return false;}
}const user2 = new SecureUser('Bob', 'password456');// 公有字段可访问
console.log(user2.name); // 'Bob'// 私有字段不可外部访问
console.log(user2.#password); // SyntaxError: Private field '#password' must be declared in an enclosing class
console.log(user2.#updateLastLogin()); // SyntaxError// 只能通过公有方法访问/操作私有成员
console.log(user2.checkPassword('password456')); // true
console.log(user2.getLastLogin()); // 登录前的时间
user2.login('password456');
console.log(user2.getLastLogin()); // 登录后的最新时间// 私有字段的继承限制
class AdminUser extends SecureUser {constructor(name, password, role) {super(name, password);this.role = role;}// 子类无法访问父类的私有字段getParentPassword() {return this.#password; // SyntaxError: Private field '#password' is not defined in class 'AdminUser'}
}

2. 顶层 await 正式标准化(ES2020提案,ES2022正式纳入)

// 模块顶层直接使用await,无需包裹在async函数中
// config.js
try {// 顶层await加载远程配置const response = await fetch('https://api.example.com/config');const config = await response.json();// 导出加载完成的配置export const appConfig = {apiUrl: config.apiUrl || 'https://default-api.example.com',theme: config.theme || 'light',features: config.features || []};
} catch (error) {// 加载失败时导出默认配置console.error('配置加载失败,使用默认配置:', error);export const appConfig = {apiUrl: 'https://default-api.example.com',theme: 'light',features: []};
}// db.js
import { appConfig } from './config.js';
import { Database } from './database.js';// 顶层await初始化数据库连接
export const db = await Database.connect({host: appConfig.apiUrl,timeout: 5000
});// main.js
import { appConfig } from './config.js';
import { db } from './db.js';// 导入时config和db已初始化完成,可直接使用
console.log('应用启动,使用API地址:', appConfig.apiUrl);// 直接使用已连接的数据库
async function getUsers() {const users = await db.query('SELECT * FROM users LIMIT 10');return users;
}// 注意事项:
// 1. 顶层await仅在ES模块中支持(需设置type="module")
// 2. 会阻塞模块依赖链,适用于必要的初始化场景
// 3. 避免循环依赖中的顶层await

3. Error.cause - 错误链追踪

// ES2022之前:错误原因难以追踪
function fetchData() {return fetch('/api/data').then(response => {if (!response.ok) {throw new Error(`HTTP错误: ${response.status}`);}return response.json();}).catch(error => {// 原始错误信息被覆盖,难以定位根本原因throw new Error('数据获取失败');});
}// ES2022:Error.cause 传递原始错误
function fetchDataWithCause() {return fetch('/api/data').then(response => {if (!response.ok) {throw new Error(`HTTP错误: ${response.status}`, {cause: new Error(`响应状态: ${response.status}, 响应文本: ${response.statusText}`)});}return response.json();}).catch(error => {// 保留原始错误,添加上下文信息throw new Error('数据获取失败', { cause: error });});
}// 使用错误链
fetchDataWithCause().catch(error => {console.error('最终错误:', error.message); // 最终错误: 数据获取失败// 追踪原始错误链let cause = error.cause;let depth = 1;while (cause) {console.error(`原因 ${depth}:`, cause.message);cause = cause.cause;depth++;}// 输出示例:// 原因 1: HTTP错误: 404// 原因 2: 响应状态: 404, 响应文本: Not Found});// 实际应用:多层级错误处理
async function processOrder(orderId) {try {const order = await fetchOrder(orderId);try {await validateOrder(order);} catch (validateError) {throw new Error(`订单验证失败 (ID: ${orderId})`, { cause: validateError });}try {await processPayment(order);} catch (paymentError) {throw new Error(`支付处理失败 (ID: ${orderId})`, { cause: paymentError });}return { success: true, orderId };} catch (error) {// 记录完整错误链logErrorWithCause(error);throw error;}
}// 错误日志记录函数
function logErrorWithCause(error) {const errorChain = [{ message: error.message, stack: error.stack }];let cause = error.cause;while (cause) {errorChain.push({ message: cause.message, stack: cause.stack });cause = cause.cause;}// 发送完整错误链到日志服务fetch('/api/logs', {method: 'POST',body: JSON.stringify({timestamp: new Date().toISOString(),errorChain})});
}

4. Array.prototype.at()

const arr = ['a', 'b', 'c', 'd', 'e'];// ES2022之前:访问数组末尾元素
console.log(arr[arr.length - 1]); // 'e'(传统方式)
console.log(arr.slice(-1)[0]); // 'e'(slice方式)// ES2022 at(): 支持负索引,更简洁
console.log(arr.at(0)); // 'a'(正索引,同arr[0])
console.log(arr.at(2)); // 'c'(正索引)
console.log(arr.at(-1)); // 'e'(负索引,最后一个元素)
console.log(arr.at(-2)); // 'd'(负索引,倒数第二个元素)
console.log(arr.at(-5)); // 'a'(负索引,第一个元素)
console.log(arr.at(-6)); // undefined(超出范围)// 实际应用:处理用户输入的列表
function getLastSelectedItem(selectedItems) {// 安全获取最后一个选中项,无需判断数组长度return selectedItems.at(-1) || '无选中项';
}const selected = ['item1', 'item2', 'item3'];
console.log(getLastSelectedItem(selected)); // 'item3'const emptySelected = [];
console.log(getLastSelectedItem(emptySelected)); // '无选中项'// 字符串也支持at()方法
const str = 'Hello World';
console.log(str.at(-1)); // 'd'(最后一个字符)
console.log(str.at(-5)); // 'W'(倒数第五个字符)// 类数组对象也支持
const argumentsObj = function() { return arguments; }('a', 'b', 'c');
console.log(Array.prototype.at.call(argumentsObj, -1)); // 'c'

5. Object.hasOwn()

const user = {name: 'Alice',age: 30
};// 继承的属性
Object.prototype.customMethod = function() {};// ES2022之前:判断自身属性(排除继承属性)
console.log(user.hasOwnProperty('name')); // true(自身属性)
console.log(user.hasOwnProperty('customMethod')); // false(继承属性)// 问题:如果对象重写了hasOwnProperty方法,会导致错误
const badObj = {hasOwnProperty: () => false,value: 'test'
};console.log(badObj.hasOwnProperty('value')); // false(错误结果,因为hasOwnProperty被重写)// ES2022 Object.hasOwn(): 更安全的自身属性判断
console.log(Object.hasOwn(user, 'name')); // true(自身属性)
console.log(Object.hasOwn(user, 'customMethod')); // false(继承属性)// 解决重写hasOwnProperty的问题
console.log(Object.hasOwn(badObj, 'value')); // true(正确结果)
console.log(Object.hasOwn(badObj, 'hasOwnProperty')); // true(自身属性)// 处理null/undefined(不会报错)
console.log(Object.hasOwn(null, 'any')); // false
console.log(Object.hasOwn(undefined, 'any')); // false// 实际应用:安全遍历对象自身属性
function getOwnProperties(obj) {const props = [];for (const key in obj) {// 安全判断自身属性if (Object.hasOwn(obj, key)) {props.push(key);}}return props;
}const testObj = {a: 1,b: 2
};// 添加继承属性
testObj.__proto__.c = 3;console.log(getOwnProperties(testObj)); // ['a', 'b'](正确排除继承属性c)

6. 模块的静态导入和导出增强

// 1. 导出时重命名的增强
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;// 导出时重命名
export { add as sum, subtract as difference };// 2. 导入时重命名和聚合导出
// utils.js
export { sum, difference } from './math.js'; // 聚合导出
export { default as formatDate } from './date-utils.js'; // 导出默认模块并命名// 3. 动态导入的增强(结合顶层await)
// feature.js
let featureModule;if (process.env.FEATURE_ENABLED) {// 条件动态导入featureModule = await import('./feature-module.js');
} else {featureModule = await import('./feature-fallback.js');
}// 导出动态导入的模块
export const feature = featureModule;// 4. 导入断言(Import Assertions)
// 导入JSON文件(需运行环境支持)
import config from './config.json' assert { type: 'json' };console.log(config.apiUrl); // 使用JSON数据// 导入CSS模块(在浏览器或构建工具中)
import styles from './styles.css' assert { type: 'css' };// 5. 命名空间导出的增强
// components.js
export * as Buttons from './buttons.js';
export * as Inputs from './inputs.js';
export * as Modals from './modals.js';// 使用时
import { Buttons, Inputs } from './components.js';const primaryBtn = new Buttons.PrimaryButton();
const textInput = new Inputs.TextInput();

ES2023 (ES14) - 数组操作和性能优化

1. 数组不可变方法 (toReversed(), toSorted(), toSpliced(), with())

const arr = [3, 1, 2];// 1. toReversed() - 反转数组(不改变原数组)
const reversed = arr.toReversed();
console.log(reversed); // [2, 1, 3]
console.log(arr); // [3, 1, 2](原数组不变)// 对比传统reverse()(改变原数组)
const arr2 = [3, 1, 2];
arr2.reverse();
console.log(arr2); // [2, 1, 3](原数组改变)// 2. toSorted() - 排序数组(不改变原数组)
const sorted = arr.toSorted();
console.log(sorted); // [1, 2, 3]
console.log(arr); // [3, 1, 2](原数组不变)// 自定义排序
const users = [{ name: 'Bob', age: 25 },{ name: 'Alice', age: 30 },{ name: 'Charlie', age: 20 }
];const sortedByAge = users.toSorted((a, b) => a.age - b.age);
console.log(sortedByAge); 
// [
//   { name: 'Charlie', age: 20 },
//   { name: 'Bob', age: 25 },
//   { name: 'Alice', age: 30 }
// ]
console.log(users); // 原数组不变// 3. toSpliced() - 删除/插入元素(不改变原数组)
const arr3 = [1, 2, 3, 4, 5];// 删除:从索引1开始删除2个元素
const spliced1 = arr3.toSpliced(1, 2);
console.log(spliced1); // [1, 4, 5]
console.log(arr3); // [1, 2, 3, 4, 5](原数组不变)// 插入:从索引2开始删除0个元素,插入6,7
const spliced2 = arr3.toSpliced(2, 0, 6, 7);
console.log(spliced2); // [1, 2, 6, 7, 3, 4, 5]// 替换:从索引3开始删除1个元素,插入8
const spliced3 = arr3.toSpliced(3, 1, 8);
console.log(spliced3); // [1, 2, 3, 8, 5]// 4. with () - 替换数组元素(不改变原数组)
const arr = [10, 20, 30, 40];// 替换索引1的元素为25
const newArr = arr.with(1, 25);
console.log(newArr); // [10, 25, 30, 40]
console.log(arr);    // [10, 20, 30, 40](原数组未改变)// 替换最后一个元素(可结合负索引)
const arr2 = ['a', 'b', 'c'];
const updatedArr = arr2.with(-1, 'd');
console.log(updatedArr); // ['a', 'b', 'd']// 超出数组长度的索引:自动扩展数组(填补 empty 空位)
const arr3 = [1, 2];
const extendedArr = arr3.with(5, 6);
console.log(extendedArr); // [1, 2, empty × 3, 6]
console.log(extendedArr.length); // 6(数组长度自动调整)

2. Symbols 作为 WeakMap 键

在 ES2023 之前,WeakMap 的键只能是对象类型ObjectArrayFunction 等),无法使用 Symbol。ES2023 扩展了这一限制,允许 Symbol 作为 WeakMap 的键,解决了“需要唯一标识且不影响垃圾回收”的场景需求。

核心特性
  • Symbol 作为键时,仍保持 WeakMap 的“弱引用”特性:若 Symbol 没有其他强引用,对应的 WeakMap 条目会被垃圾回收。
  • Symbol 的唯一性保证:即使两个 Symbol 描述相同(如 Symbol('key')),也会被视为不同的键,避免键冲突。
// ES2023 之前:WeakMap 键只能是对象
const weakMapOld = new WeakMap();
const objKey = {};
weakMapOld.set(objKey, '对象作为键'); // 合法
// weakMapOld.set(Symbol('key'), 'Symbol作为键'); // 报错(ES2023前不支持)// ES2023:Symbol 可作为 WeakMap 键
const weakMap = new WeakMap();// 1. 基本使用:Symbol 作为键
const symbolKey1 = Symbol('userData');
const symbolKey2 = Symbol('config');// 设置值
weakMap.set(symbolKey1, { name: 'Alice', age: 30 });
weakMap.set(symbolKey2, { theme: 'dark', fontSize: 16 });// 获取值
console.log(weakMap.get(symbolKey1)); // { name: 'Alice', age: 30 }
console.log(weakMap.has(symbolKey2)); // true// 删除值
weakMap.delete(symbolKey2);
console.log(weakMap.has(symbolKey2)); // false// 2. 唯一性保证:描述相同的 Symbol 是不同的键
const symA = Symbol('sameDesc');
const symB = Symbol('sameDesc');
weakMap.set(symA, '值A');
weakMap.set(symB, '值B');
console.log(weakMap.get(symA)); // '值A'(与symB不冲突)
console.log(weakMap.get(symB)); // '值B'// 3. 弱引用特性:Symbol 无强引用时,条目会被垃圾回收
let tempSymbol = Symbol('temp');
weakMap.set(tempSymbol, '临时数据');
console.log(weakMap.has(tempSymbol)); // true// 释放强引用:tempSymbol 不再指向该 Symbol
tempSymbol = null;
// 垃圾回收后:weakMap 中该条目会被自动清理(时机由JS引擎决定)
setTimeout(() => {console.log(weakMap.has(tempSymbol)); // 可能为 false(已回收)
}, 1000);// 实际应用:模块私有状态管理
// 场景:模块内需要存储私有状态,且不希望暴露给外部,同时支持垃圾回收
const moduleWeakMap = new WeakMap();// 私有 Symbol 键(模块内隐藏,外部无法访问)
const privateStateKey = Symbol('privateState');// 公开方法:初始化模块状态
export function initModule() {const state = { count: 0, logs: [] };moduleWeakMap.set(privateStateKey, state);
}// 公开方法:更新模块状态(外部无法直接访问 state)
export function incrementCount() {const state = moduleWeakMap.get(privateStateKey);if (state) {state.count++;state.logs.push(`更新时间:${new Date().toISOString()}`);}
}// 公开方法:获取状态(仅暴露必要信息)
export function getModuleState() {const state = moduleWeakMap.get(privateStateKey);return state ? { count: state.count, logCount: state.logs.length } : null;
}

3. Hashbang 语法(#!)支持

Hashbang(也叫 Shebang)是 Unix-like 系统中用于指定脚本解释器的语法(如 #!/usr/bin/env node)。ES2023 正式将其纳入标准,允许 JavaScript 脚本文件开头使用 Hashbang,且 JS 引擎会自动忽略这一行(无需手动处理)。

核心作用
  • 让 JS 脚本可直接在终端执行(如 ./script.js),无需显式调用 node script.js
  • 标准化 Hashbang 处理:避免不同 JS 引擎对 Hashbang 的解析差异。
// script.js(ES2023 支持 Hashbang)
#!/usr/bin/env node// 脚本逻辑(Hashbang 行被 JS 引擎自动忽略)
console.log('Hello, Hashbang!');// 命令行执行(无需 node 命令)
// 1. 给脚本添加可执行权限:chmod +x script.js
// 2. 直接执行:./script.js
// 输出:Hello, Hashbang!// 注意事项:
// 1. Hashbang 必须在文件第一行,且以 #! 开头
// 2. 仅在 Unix-like 系统(Linux、macOS)生效,Windows 需通过 WSL 或 Git Bash 等环境支持
// 3. 若脚本在浏览器中运行,Hashbang 行仍会被忽略(不影响前端代码)// 实际应用:CLI 工具脚本
#!/usr/bin/env node// 简单的 CLI 工具:接收命令行参数并输出
const args = process.argv.slice(2); // 获取命令行参数(排除 node 和脚本路径)if (args.length === 0) {console.log('请输入参数!用法:./cli.js <消息>');process.exit(1);
}console.log(`你输入的消息:${args.join(' ')}`);
console.log(`当前时间:${new Date().toLocaleString()}`);// 执行示例:
// ./cli.js Hello ES2023
// 输出:
// 你输入的消息:Hello ES2023
// 当前时间:2024/5/20 14:30:00

4. 其他小优化

(1)Array.prototype.findLast()Array.prototype.findLastIndex()

ES2022 已引入这两个方法,但 ES2023 进一步优化了其兼容性和性能。它们从数组末尾开始查找元素,避免了传统“反转数组后查找”的额外开销。

const numbers = [10, 20, 30, 40, 50];// 从末尾查找第一个大于 30 的元素
const lastLargeNum = numbers.findLast(num => num > 30);
console.log(lastLargeNum); // 50// 从末尾查找第一个大于 30 的元素的索引
const lastLargeIndex = numbers.findLastIndex(num => num > 30);
console.log(lastLargeIndex); // 4(索引从 0 开始)// 实际应用:查找最新的有效数据
const logs = [{ id: 1, status: 'failed' },{ id: 2, status: 'success' },{ id: 3, status: 'failed' },{ id: 4, status: 'success' }
];// 查找最后一次成功的日志
const lastSuccessLog = logs.findLast(log => log.status === 'success');
console.log(lastSuccessLog); // { id: 4, status: 'success' }
(2)TypedArray 方法扩展

ES2023 为 TypedArray(如 Uint8ArrayFloat64Array 等)添加了与普通数组一致的方法,如 toReversed()toSorted()toSpliced()with(),确保类型化数组也能支持不可变操作。

// 创建一个 TypedArray(无符号8位整数数组)
const typedArr = new Uint8Array([3, 1, 2]);// 不可变排序
const sortedTypedArr = typedArr.toSorted();
console.log(sortedTypedArr); // Uint8Array [1, 2, 3]
console.log(typedArr);       // Uint8Array [3, 1, 2](原数组不变)// 不可变替换
const updatedTypedArr = typedArr.with(1, 5);
console.log(updatedTypedArr); // Uint8Array [3, 5, 2]

ES2024 (ES15) - 高效数据处理与异步增强

1. 数组分组 API(Array.prototype.group()Array.prototype.groupToMap()

ES2024 引入了原生的数组分组方法,解决了传统“手动循环+条件判断”分组的繁琐问题,支持直接按条件将数组分为多个子集。

(1)Array.prototype.group(callback)
  • 接收一个回调函数,回调返回字符串/符号(Symbol)类型的分组键
  • 返回一个普通对象,键为分组键,值为该组对应的数组元素。
const products = [{ name: 'iPhone', category: 'electronics', price: 999 },{ name: 'Shirt', category: 'clothing', price: 29 },{ name: 'Laptop', category: 'electronics', price: 1299 },{ name: 'Pants', category: 'clothing', price: 49 },{ name: 'Headphones', category: 'electronics', price: 199 }
];// 按 category 分组
const groupedByCategory = products.group(product => product.category);
console.log(groupedByCategory);
// {
//   electronics: [
//     { name: 'iPhone', category: 'electronics', price: 999 },
//     { name: 'Laptop', category: 'electronics', price: 1299 },
//     { name: 'Headphones', category: 'electronics', price: 199 }
//   ],
//   clothing: [
//     { name: 'Shirt', category: 'clothing', price: 29 },
//     { name: 'Pants', category: 'clothing', price: 49 }
//   ]
// }// 按价格区间分组(自定义分组键)
const groupedByPrice = products.group(product => {if (product.price < 100) return 'cheap';if (product.price < 1000) return 'mid';return 'expensive';
});
console.log(groupedByPrice.cheap); // [Shirt, Pants]
console.log(groupedByPrice.expensive); // [Laptop]
(2)Array.prototype.groupToMap(callback)
  • group() 逻辑类似,但返回一个 Map 对象(而非普通对象)。
  • 支持任意类型的分组键(如对象、数组),解决了普通对象键只能是字符串/Symbol 的限制。
const users = [{ name: 'Alice', age: 25, team: { id: 1, name: 'Team A' } },{ name: 'Bob', age: 30, team: { id: 2, name: 'Team B' } },{ name: 'Charlie', age: 28, team: { id: 1, name: 'Team A' } },{ name: 'David', age: 32, team: { id: 2, name: 'Team B' } }
];// 按 team 对象分组(普通 group() 无法实现,因为对象键会被转为 "[object Object]")
const groupedByTeam = users.groupToMap(user => user.team);// Map 的键是 team 对象,值是该团队的用户数组
const teamAUsers = groupedByTeam.get(users[0].team);
console.log(teamAUsers); // [Alice, Charlie]// 遍历 Map 分组结果
groupedByTeam.forEach((usersInTeam, team) => {console.log(`团队 ${team.name} 成员:`, usersInTeam.map(u => u.name));
});
// 输出:
// 团队 Team A 成员: ["Alice", "Charlie"]
// 团队 Team B 成员: ["Bob", "David"]

2. Promise.withResolvers() - 简化 Promise 创建

传统创建 Promise 时,需手动定义 resolvereject 函数并封装在 executor 回调中。ES2024 的 Promise.withResolvers() 直接返回包含 promiseresolvereject 的对象,简化了 Promise 初始化代码。

// 传统 Promise 创建方式
function createTimer(ms) {let resolve;const promise = new Promise((res) => {setTimeout(() => res(`延迟 ${ms}ms 完成`), ms);resolve = res; // 需手动保存 resolve 引用(如需外部触发)});return { promise, resolve };
}// ES2024 Promise.withResolvers()
function createTimerNew(ms) {const { promise, resolve } = Promise.withResolvers();setTimeout(() => resolve(`延迟 ${ms}ms 完成`), ms);return { promise, resolve };
}// 使用示例:控制 Promise 完成时机
const { promise: timerPromise, resolve: manualResolve } = createTimerNew(1000);// 监听 Promise 结果
timerPromise.then(message => console.log(message)); // 1秒后输出 "延迟 1000ms 完成"// 可选:提前手动触发 resolve(覆盖定时器逻辑)
// manualResolve("手动提前完成"); // 若取消注释,会立即输出该消息// 实际应用:异步资源锁
class AsyncLock {constructor() {this.isLocked = false;this.pending = null; // 存储等待中的 Promise  resolver}// 加锁lock() {if (!this.isLocked) {this.isLocked = true;return Promise.resolve(); // 无需等待,直接加锁}// 已有锁,返回等待中的 Promiseif (!this.pending) {this.pending = Promise.withResolvers();}return this.pending.promise;}// 解锁unlock() {this.isLocked = false;// 若有等待中的请求,触发下一个锁if (this.pending) {this.pending.resolve();this.pending = null; // 清空等待状态}}
}// 使用异步锁:确保异步操作串行执行
const lock = new AsyncLock();async function safeAsyncOperation(taskName) {await lock.lock(); // 加锁:若已有操作,等待其完成try {console.log(`开始执行任务:${taskName}`);await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟异步操作console.log(`完成任务:${taskName}`);} finally {lock.unlock(); // 确保解锁(即使出错)}
}// 并发调用,但会串行执行
safeAsyncOperation('任务1');
safeAsyncOperation('任务2');
safeAsyncOperation('任务3');
// 输出顺序:
// 开始执行任务:任务1 → 完成任务:任务1 → 开始执行任务:任务2 → 完成任务:任务2 → ...

3. Temporal API - 彻底解决日期时间处理痛点

JavaScript 原生的 Date 对象长期存在设计缺陷(如月份从 0 开始、时区处理混乱、无法处理历法等)。ES2024 引入的 Temporal API 是一套全新的日期时间处理标准,提供了清晰、安全、易用的 API,支持时区、历法、时长等复杂场景。

核心概念与常用 API
类型用途示例
Temporal.Instant表示“时间点”(UTC 时间,无时区)Temporal.Instant.from('2024-05-20T12:00:00Z')
Temporal.ZonedDateTime带时区的日期时间(如“北京时间 2024-05-20 20:00:00”)Temporal.ZonedDateTime.from('2024-05-20T20:00:00+08:00[Asia/Shanghai]')
Temporal.PlainDate无时间、无时区的日期(如“2024-05-20”)Temporal.PlainDate.from('2024-05-20')
Temporal.PlainTime无日期、无时区的时间(如“14:30:00”)Temporal.PlainTime.from('14:30:00')
Temporal.Duration表示“时长”(如“2小时30分钟”)Temporal.Duration.from({ hours: 2, minutes: 30 })
代码示例
// 1. 创建带时区的日期时间(解决 Date 时区混乱问题)
// 北京时区的 2024年5月20日 20:00:00
const beijingTime = Temporal.ZonedDateTime.from({year: 2024,month: 5,day: 20,hour: 20,minute: 0,second: 0,timeZone: 'Asia/Shanghai' // 明确指定时区
});
console.log(beijingTime.toString()); // 2024-05-20T20:00:00+08:00[Asia/Shanghai]// 转换为纽约时区
const newYorkTime = beijingTime.toTimeZone('America/New_York');
console.log(newYorkTime.toString()); // 2024-05-20T08:00:00-04:00[America/New_York](自动计算时差)// 2. 日期计算(避免 Date 的月份偏移问题)
const today = Temporal.PlainDate.from('2024-05-20');// 加 1 个月(自动处理月份天数差异)
const nextMonth = today.add({ months: 1 });
console.log(nextMonth.toString()); // 2024-06-20// 减 2 周
const twoWeeksAgo = today.subtract({ weeks: 2 });
console.log(twoWeeksAgo.toString()); // 2024-05-06// 计算两个日期的差值(返回 Duration)
const diff = nextMonth.since(today);
console.log(diff.months); // 1(差值为 1 个月)// 3. 格式化(内置支持,无需第三方库如 moment.js)
const zonedTime = Temporal.ZonedDateTime.from('2024-05-20T20:00:00+08:00[Asia/Shanghai]');// 自定义格式
console.log(zonedTime.toLocaleString('zh-CN', {year: 'numeric',month: 'long',day: 'numeric',hour: '2-digit',minute: '2-digit',timeZoneName: 'long'
})); // 2024年5月20日 20:00 中国标准时间// 4. 处理不同历法(如农历、伊斯兰历)
// 注意:部分历法需额外加载插件,核心 API 支持扩展
const lunarDate = Temporal.PlainDate.from({year: 2024,month: 4,day: 13,calendar: 'chinese' // 农历(需环境支持)
});
console.log(lunarDate.toLocaleString('zh-CN')); // 2024年四月十三(农历)// 5. 时长处理(精确到纳秒,支持复杂单位)
const duration1 = Temporal.Duration.from({ hours: 2, minutes: 30 });
const duration2 = Temporal.Duration.from({ minutes: 45, seconds: 15 });// 时长相加
const totalDuration = duration1.add(duration2);
console.log(totalDuration.toString()); // PT3H15M15S(3小时15分15秒)// 时长转换为总秒数
console.log(totalDuration.total({ unit: 'seconds' })); // 11715(3*3600 + 15*60 +15)

ES2025 (ES16) - 提案中的重要特性

ES2025 的特性目前处于 Stage 3 提案阶段(接近标准化),以下是最受关注的两个特性:

1. Iterator Helpers - 迭代器辅助方法

迭代器(Iterator)是 JavaScript 中处理序列数据的核心接口(如数组、Map、生成器函数返回值),但原生缺乏便捷的操作方法。Iterator Helpers 为迭代器添加了类似数组的链式操作方法(如 map()filter()take()),支持惰性求值(仅在需要时计算下一个元素),大幅提升迭代器的易用性。

// 1. 基本使用:迭代器链式操作
const numbers = [1, 2, 3, 4, 5];
const iterator = numbers[Symbol.iterator](); // 获取数组的迭代器// 迭代器操作链:过滤偶数 → 乘以 2 → 取前 2 个元素
const resultIterator = iterator.filter(num => num % 2 === 0) // 过滤偶数:2,4.map(num => num * 2) // 乘以2:4,8.take(2); // 取前2个元素// 遍历结果(惰性求值:仅在 next() 调用时计算)
console.log(resultIterator.next().value); // 4(第一次计算)
console.log(resultIterator.next().value); // 8(第二次计算)
console.log(resultIterator.next().done); // true(无更多元素)// 2. 与生成器函数结合(处理无限序列)
// 生成无限递增的整数迭代器
function* infiniteNumbers() {let num = 1;while (true) yield num++;
}// 操作无限迭代器:取偶数 → 乘以 3 → 取前 3 个
const finiteResult = infiniteNumbers().filter(num => num % 2 === 0).map(num => num * 3).take(3);// 转换为数组(触发迭代器计算)
console.log(Array.from(finiteResult)); // [6, 12, 18](仅计算前3个,避免无限循环)// 3. 异步迭代器支持(Async Iterator)
async function* asyncDataGenerator() {yield Promise.resolve(1);yield Promise.resolve(2);yield Promise.resolve(3);
}// 异步迭代器操作:过滤大于1的数 → 乘以 10
const asyncResult = asyncDataGenerator().filter(async num => (await num) > 1).map(async num => (await num) * 10);// 遍历异步结果
for await (const value of asyncResult) {console.log(value); // 20 → 30
}

2. Promise.try() - 简化同步/异步错误捕获

传统场景中,若一个函数可能返回 同步值Promise,捕获其错误需同时处理 try/catch(同步)和 .catch()(异步),代码冗余。Promise.try() 统一了这一逻辑:无论函数返回同步值还是 Promise,都能通过 .catch() 捕获所有错误。

// 传统问题:函数可能返回同步值或 Promise,错误捕获繁琐
function unstableFunction(shouldThrow, isAsync) {if (shouldThrow) {// 可能抛出同步错误throw new Error('同步错误');}if (isAsync) {// 可能返回 rejected Promisereturn Promise.reject(new Error('异步错误'));}// 可能返回同步值return '成功结果';
}// 传统错误捕获方式(需同时处理同步和异步)
function handleTraditional() {try {const result = unstableFunction(false, true);// 若返回 Promise,需额外 catchif (result instanceof Promise) {result.catch(error => console.error('传统捕获:', error.message));}} catch (error) {console.error('传统捕获:', error.message);}
}// ES2025 Promise.try():统一捕获同步/异步错误
function handleWithTry() {Promise.try(() => unstableFunction(false, true)).then(result => console.log('成功:', result)).catch(error => console.error('Promise.try 捕获:', error.message));
}// 测试不同场景
handleWithTry(false, false); // 成功: 成功结果(同步值)
handleWithTry(true, false);  // 捕获: 同步错误(同步抛出)
handleWithTry(false, true);  // 捕获: 异步错误(Promise reject)// 实际应用:统一处理 API 调用(可能有缓存层返回同步值)
function fetchDataWithCache(id) {// 1. 先查缓存(同步)const cachedData = getFromCache(id);if (cachedData) {return cachedData; // 同步返回缓存值}// 2. 缓存未命中,异步请求return fetch(`/api/data/${id}`).then(res => res.json());
}// 使用 Promise.try() 统一处理
Promise.try(() => fetchDataWithCache(123)).then(data => console.log('数据:', data)).catch(error => {// 同时捕获:缓存读取错误(同步)和 API 请求错误(异步)console.error('获取数据失败:', error);});

总结与学习建议

ECMAScript 从 2015 年开始的“年度更新”模式,让 JavaScript 逐步成为一门更成熟、更强大的语言。每个版本的新特性都围绕“解决实际开发痛点”展开,如:

  • ES2015 奠定现代 JS 基础(let/const、箭头函数、模块);
  • ES2017-2020 优化异步编程(async/await、可选链、顶层 await);
  • ES2022-2024 增强安全性与不可变性(私有字段、数组不可变方法);
  • ES2024+ 聚焦高效数据处理(分组 API、Temporal)。
http://www.xdnf.cn/news/18656.html

相关文章:

  • 在压力测试中如何确定合适的并发用户数?
  • 挖币与区块链技术有怎样的联系?
  • 基于 Prometheus+Alertmanager+Grafana 打造监控报警后台(一)-Prometheus介绍及安装
  • DMP-Net:面向脑组织术中成像的深度语义先验压缩光谱重建方法|文献速递-深度学习人工智能医疗图像
  • PyTorch实战(1)——深度学习概述
  • 阿里:基于设计逻辑的LLM数据合成
  • crc16是什么算法
  • C++ 指针与引用面试深度解析
  • STM32项目分享:基于STM32的智能洗衣机
  • 开源大模型天花板?DeepSeek-V3 6710亿参数MoE架构深度拆解
  • 微软恶意软件删除工具:官方免费的系统安全防护利器
  • 网络编程1-基本概念、函数接口
  • 2.1.5 数学与其他
  • VUE 的弹出框实现图片预览和视频预览
  • C++数据结构之二叉搜索树
  • AEB 强制来临,东软睿驰Next-Cube-Lite有望成为汽车安全普惠“破局器”
  • macbook国内源安装rust
  • 【AGI使用教程】GPT-OSS 本地部署(2)
  • 【AMBA总线互联IP】
  • 自然语言处理——07 BERT、ELMO、GTP系列模型
  • python文件import找不到其它目录的库解决方案
  • Python爬虫第四课:selenium自动化
  • 【云馨AI-大模型】AI热潮持续升温:2025年8月第三周全球动态
  • MySQL数据库精研之旅第十一期:打造高效联合查询的实战宝典(二)
  • 禁用 Nagle 算法(TCP_NODELAY)
  • RuoYi-Vue3项目中Swagger接口测试404,端口问题解析排查
  • 信誉代币的发行和管理机制是怎样的?
  • linux下camera 详细驱动流程 OV02K10为例(chatgpt版本)
  • stm32温控大棚测控系统(CO2+温湿度+光照)+仿真
  • Linux->多线程2