ES6/ES2015 - ES16/ES2025

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存在变量提升和函数作用域问题。

javascript 复制代码
// 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. 箭头函数 - 简洁的函数语法

javascript 复制代码
// 传统函数
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类 - 面向对象编程

javascript 复制代码
// 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. 模板字符串 - 字符串插值

javascript 复制代码
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. 解构赋值 - 提取数据的便捷方式

javascript 复制代码
// 数组解构
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

javascript 复制代码
// 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 - 异步编程

javascript 复制代码
// 创建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. 指数运算符 (**)

javascript 复制代码
// 传统方式
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()

javascript 复制代码
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 - 异步代码同步写法

javascript 复制代码
// 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()

javascript 复制代码
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()

javascript 复制代码
// 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)

javascript 复制代码
// 创建异步迭代器
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()

javascript 复制代码
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. 对象扩展运算符

javascript 复制代码
// 对象扩展运算符
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. 正则表达式增强

javascript 复制代码
// 命名捕获组
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()

javascript 复制代码
// 数组扁平化
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()

javascript 复制代码
// 将键值对数组转换为对象
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()

javascript 复制代码
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 参数

javascript 复制代码
// 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() 增强

javascript 复制代码
// 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 - 任意精度整数

javascript 复制代码
// 传统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. 空值合并运算符 (??)

javascript 复制代码
// 传统逻辑运算符的问题(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. 可选链运算符 (?.)

javascript 复制代码
// 传统嵌套属性访问的问题
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()

javascript 复制代码
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())

javascript 复制代码
// 传统静态导入:无论是否需要都会加载
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)

javascript 复制代码
// 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()

javascript 复制代码
// 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()

javascript 复制代码
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. 逻辑赋值操作符 (&&=, ||=, ??=)

javascript 复制代码
// 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)

javascript 复制代码
// 传统大数字:难以阅读
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

javascript 复制代码
// 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. 类的私有字段 (#)

javascript 复制代码
// 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正式纳入)

javascript 复制代码
// 模块顶层直接使用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 - 错误链追踪

javascript 复制代码
// 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()

javascript 复制代码
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()

javascript 复制代码
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. 模块的静态导入和导出增强

javascript 复制代码
// 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())

javascript 复制代码
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')),也会被视为不同的键,避免键冲突。
javascript 复制代码
// 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 的解析差异。
javascript 复制代码
// 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 进一步优化了其兼容性和性能。它们从数组末尾开始查找元素,避免了传统"反转数组后查找"的额外开销。

javascript 复制代码
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(),确保类型化数组也能支持不可变操作。

javascript 复制代码
// 创建一个 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)类型的分组键
  • 返回一个普通对象,键为分组键,值为该组对应的数组元素。
javascript 复制代码
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 的限制。
javascript 复制代码
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 初始化代码。

javascript 复制代码
// 传统 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 })
代码示例
javascript 复制代码
// 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()),支持惰性求值(仅在需要时计算下一个元素),大幅提升迭代器的易用性。

javascript 复制代码
// 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() 捕获所有错误。

javascript 复制代码
// 传统问题:函数可能返回同步值或 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)。
相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼15 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax