ECMAScript 版本变迁:从 ES5 到 ES2024 的语法演进

这一篇梳理 ECMAScript 从 ES5(2009)到 ES2024 的主要版本变迁,
重点关注每个版本引入的新语法特性、API 变化,以及这些变化对前端开发的实际影响。

ES5 是 JavaScript 历史上一个重要的里程碑,它奠定了现代 JavaScript 的基础。

  • 严格模式(Strict Mode)
    'use strict' 指令,让 JavaScript 引擎以更严格的规则解析代码,减少一些历史遗留的“坑”。

  • JSON 支持
    JSON.parse()JSON.stringify(),让 JavaScript 原生支持 JSON 数据格式。

  • 数组方法增强
    Array.prototype.forEachmapfilterreducesomeevery 等函数式编程方法。

  • 对象属性描述符
    Object.definePropertyObject.defineProperties,可以精确控制对象属性的行为(可枚举、可写、可配置)。

  • Getter/Setter
    对象属性的访问器,可以自定义属性的读取和写入行为。

 1// ES5 严格模式
 2'use strict';
 3var x = 1; // 必须声明变量
 4
 5// ES5 数组方法
 6var numbers = [1, 2, 3, 4, 5];
 7var doubled = numbers.map(function(n) {
 8  return n * 2;
 9});
10
11// ES5 对象属性描述符
12var obj = {};
13Object.defineProperty(obj, 'name', {
14  value: 'John',
15  writable: false,
16  enumerable: true,
17  configurable: true
18});

ES6 是 JavaScript 历史上变化最大的版本,引入了大量新语法和特性。

  • let 和 const
    块级作用域变量声明,解决了 var 的作用域问题。

  • 箭头函数
    () => {} 语法,简化函数定义,自动绑定 this

  • 模板字符串
    反引号 `${} 插值,支持多行字符串。

  • 解构赋值
    数组和对象的解构,简化变量提取。

  • 默认参数
    函数参数可以设置默认值。

  • 剩余参数和展开运算符
    ...args 用于收集参数或展开数组/对象。

  • 类(Class)
    class 语法,提供更清晰的面向对象编程方式。

  • 模块系统
    importexport,原生支持模块化。

  • Promise
    异步编程的基础,解决回调地狱问题。

  • Symbol
    新的原始类型,用于创建唯一的标识符。

  • Set 和 Map
    新的数据结构,Set 用于存储唯一值,Map 用于键值对。

 1// ES6 let/const
 2let x = 1;
 3const y = 2;
 4
 5// ES6 箭头函数
 6const add = (a, b) => a + b;
 7
 8// ES6 模板字符串
 9const name = 'John';
10const message = `Hello, ${name}!`;
11
12// ES6 解构赋值
13const [a, b] = [1, 2];
14const { name, age } = { name: 'John', age: 30 };
15
16// ES6 类
17class Person {
18  constructor(name) {
19    this.name = name;
20  }
21  greet() {
22    return `Hello, I'm ${this.name}`;
23  }
24}
25
26// ES6 模块
27export const foo = 'bar';
28import { foo } from './module.js';
29
30// ES6 Promise
31const promise = new Promise((resolve, reject) => {
32  setTimeout(() => resolve('done'), 1000);
33});
  • Array.prototype.includes
    判断数组是否包含某个元素。

  • 指数运算符
    ** 运算符,用于幂运算。

1// ES2016 Array.includes
2[1, 2, 3].includes(2); // true
3
4// ES2016 指数运算符
52 ** 3; // 8
  • async/await
    基于 Promise 的异步语法,让异步代码看起来像同步代码。

  • Object.entries / Object.values
    获取对象的键值对数组和值数组。

  • 字符串填充方法
    String.prototype.padStartpadEnd

 1// ES2017 async/await
 2async function fetchData() {
 3  const response = await fetch('/api/data');
 4  const data = await response.json();
 5  return data;
 6}
 7
 8// ES2017 Object.entries
 9Object.entries({ a: 1, b: 2 }); // [['a', 1], ['b', 2]]
10
11// ES2017 padStart/padEnd
12'5'.padStart(3, '0'); // '005'
  • 异步迭代器
    for await...of 循环,用于遍历异步可迭代对象。

  • 对象展开运算符
    对象可以使用 ... 展开。

  • Promise.finally
    Promise 链的最终处理。

  • 正则表达式增强
    命名捕获组、后行断言等。

 1// ES2018 异步迭代器
 2async function* asyncGenerator() {
 3  yield 1;
 4  yield 2;
 5}
 6
 7for await (const value of asyncGenerator()) {
 8  console.log(value);
 9}
10
11// ES2018 对象展开
12const obj1 = { a: 1, b: 2 };
13const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
14
15// ES2018 Promise.finally
16promise
17  .then(result => console.log(result))
18  .finally(() => console.log('done'));
  • Array.prototype.flat / flatMap
    数组扁平化操作。

  • Object.fromEntries
    将键值对数组转换为对象。

  • String.prototype.trimStart / trimEnd
    字符串修剪方法。

  • 可选 catch 绑定
    catch 可以不带参数。

 1// ES2019 flat/flatMap
 2[1, [2, 3]].flat(); // [1, 2, 3]
 3[1, 2, 3].flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
 4
 5// ES2019 Object.fromEntries
 6Object.fromEntries([['a', 1], ['b', 2]]); // { a: 1, b: 2 }
 7
 8// ES2019 可选 catch 绑定
 9try {
10  // ...
11} catch {
12  // 不需要 catch (e)
13}
  • BigInt
    用于表示任意精度的整数。

  • 可选链(Optional Chaining)
    ?. 运算符,安全访问对象属性。

  • 空值合并(Nullish Coalescing)
    ?? 运算符,只在值为 nullundefined 时使用默认值。

  • 动态 import
    import() 函数,动态加载模块。

  • globalThis
    统一的全局对象引用。

 1// ES2020 BigInt
 2const bigNumber = 9007199254740991n;
 3
 4// ES2020 可选链
 5const name = user?.profile?.name;
 6
 7// ES2020 空值合并
 8const value = input ?? 'default';
 9
10// ES2020 动态 import
11const module = await import('./module.js');
12
13// ES2020 globalThis
14globalThis.console.log('Hello');
  • 逻辑赋值运算符
    &&=||=??=

  • 数字分隔符
    1_000_000 提高大数字的可读性。

  • Promise.any
    返回第一个成功的 Promise。

  • String.prototype.replaceAll
    替换所有匹配的字符串。

 1// ES2021 逻辑赋值
 2let x = 1;
 3x &&= 2; // x = 2 (因为 x 是 truthy)
 4
 5// ES2021 数字分隔符
 6const million = 1_000_000;
 7
 8// ES2021 Promise.any
 9Promise.any([promise1, promise2]).then(first => console.log(first));
10
11// ES2021 replaceAll
12'hello world'.replaceAll('l', 'L'); // 'heLLo worLd'
  • 类字段声明
    可以在类中直接声明字段,不需要在构造函数中初始化。

  • 私有字段和方法
    # 前缀,创建真正的私有成员。

  • 静态类字段和方法
    static 关键字用于类字段。

  • 顶层 await
    在模块顶层可以使用 await

  • Object.hasOwn
    更安全的 hasOwnProperty 替代。

 1// ES2022 类字段
 2class Person {
 3  name = 'John'; // 类字段
 4  #age = 30; // 私有字段
 5  
 6  static count = 0; // 静态字段
 7  
 8  #privateMethod() { // 私有方法
 9    return this.#age;
10  }
11}
12
13// ES2022 顶层 await
14const data = await fetch('/api/data');
15
16// ES2022 Object.hasOwn
17Object.hasOwn(obj, 'property');
  • Array.prototype.findLast / findLastIndex
    从数组末尾开始查找元素。

  • Array.prototype.toSorted / toReversed / toSpliced / with
    返回新数组的数组方法,不修改原数组。

1// ES2023 findLast
2[1, 2, 3, 2].findLast(x => x === 2); // 2
3
4// ES2023 toSorted (不修改原数组)
5const arr = [3, 1, 2];
6const sorted = arr.toSorted(); // [1, 2, 3], arr 仍然是 [3, 1, 2]
  • Object.groupBy / Map.groupBy
    根据函数对数组元素进行分组。

  • Promise.withResolvers
    创建带有 resolve 和 reject 的 Promise。

  • String.prototype.isWellFormed / toWellFormed
    检查和处理字符串的 Unicode 格式。

 1// ES2024 Object.groupBy
 2const people = [
 3  { name: 'John', age: 20 },
 4  { name: 'Jane', age: 25 },
 5  { name: 'Bob', age: 20 }
 6];
 7Object.groupBy(people, person => person.age);
 8// { 20: [{ name: 'John', age: 20 }, { name: 'Bob', age: 20 }], 25: [{ name: 'Jane', age: 25 }] }
 9
10// ES2024 Promise.withResolvers
11const { promise, resolve, reject } = Promise.withResolvers();

从 ES5 到 ES2024,JavaScript 的语法变得越来越简洁和表达力更强:

  • ES5 → ES6:从函数式编程的基础到现代化的语法糖(箭头函数、解构、模板字符串)。
  • ES6 → ES2017:从 Promise 到 async/await,异步编程体验大幅提升。
  • ES2017 → ES2020:可选链和空值合并让代码更安全、更简洁。
  • ES2020 → ES2024:类字段、私有成员等特性让面向对象编程更完善。

不同版本的 ES 特性需要不同的工具链支持:

  • Babel:将新语法转换为旧语法,确保浏览器兼容性。
  • TypeScript:支持所有 ES 版本特性,并提供类型检查。
  • 构建工具:Webpack、Vite 等需要配置目标 ES 版本。

虽然现代浏览器对 ES6+ 的支持已经很好了,但在实际项目中:

  • 需要考虑目标用户的浏览器版本;
  • 使用 Babel 等工具进行转译;
  • 使用 polyfill 补充缺失的 API。

不同版本的 ES 语法在 AST 中表现为不同的节点类型:

  • ES5 的函数声明FunctionDeclaration
  • ES6 的箭头函数ArrowFunction
  • ES2020 的可选链PropertyAccessExpressionElementAccessExpression(带 ? 标记)
  • ES2022 的类字段PropertyDeclaration(带 static# 标记)

在 Language Server 或代码分析工具中,需要识别这些不同的节点类型,
才能正确理解代码的语义,提供准确的补全、跳转、重构等功能。

ECMAScript 从 ES5 到 ES2024 的演进,反映了 JavaScript 语言的持续现代化:

  • 语法更简洁:箭头函数、解构、模板字符串等让代码更易读易写。
  • 异步更友好:从回调到 Promise 再到 async/await,异步编程体验不断提升。
  • 类型更安全:可选链、空值合并等特性减少了运行时错误。
  • 面向对象更完善:类字段、私有成员等让 JavaScript 的 OOP 支持更完整。

理解这些版本变迁,有助于:

  • 选择合适的 ES 版本特性来编写代码;
  • 配置正确的构建工具和转译器;
  • 在开发 Language Server 或代码分析工具时,正确处理不同版本的语法;
  • 理解为什么某些语法需要特定的 AST 节点类型和处理方式。