ES6/ES2015 - ES16/ES2025

发布于:2025-09-01 ⋅ 阅读:(23) ⋅ 点赞:(0)

ES6/ES2015 - ES16/ES2025

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

版本一览表

年份 版本 主要新特性
2015 ES6/ES2015 let/const、箭头函数、Class、模板字符串、解构赋值、模块、Promise
2016 ES7/ES2016 指数运算符、Array.includes()
2017 ES8/ES2017 async/await、Object.entries/values、padStart/padEnd
2018 ES9/ES2018 异步迭代、Promise.finally()、扩展运算符增强
2019 ES10/ES2019 Array.flat()、Object.fromEntries()、可选catch
2020 ES11/ES2020 BigInt、空值合并??、可选链?.
2021 ES12/ES2021 Promise.any()、replaceAll()、逻辑赋值操作符
2022 ES13/ES2022 私有字段、顶层await、Error.cause
2023 ES14/ES2023 数组不可变方法、Hashbang、Symbols做WeakMap键
2024 ES15/ES2024 分组API、Promise.withResolvers()、Temporal API
2025 ES16/ES2025 Iterator 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 {
  // 忽略错误,仅用于检测是否支持localStorage
  supportsLocalStorage = 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 {
    // 动态导入返回Promise
    const 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" 格式中的value
    const 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赋值给a

let x = 5;
x &&= 10; // x = 5 && 10 → 10(5为真,赋值10)
console.log(x); // 10

let 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赋值给a

let count = 0;
count ||= 10; // count = 0 || 10 → 10(0为假,赋值10)
console.log(count); // 10

let 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); // 0

let 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(); // 无需等待,直接加锁
    }

    // 已有锁,返回等待中的 Promise
    if (!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 Promise
    return Promise.reject(new Error('异步错误'));
  }
  // 可能返回同步值
  return '成功结果';
}

// 传统错误捕获方式(需同时处理同步和异步)
function handleTraditional() {
  try {
    const result = unstableFunction(false, true);
    // 若返回 Promise,需额外 catch
    if (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)。

网站公告

今日签到

点亮在社区的每一天
去签到