JavaScript 最近 10 年 (2015 - 2025)

152 阅读7分钟

引言

JavaScript 作为一门广泛应用于 Web 开发的编程语言,其发展历程中经历了多次重要的版本更新。自 2015 年起,ECMAScript 采用了每年发布一个新版本的策略,不断为 JavaScript 注入新的活力和功能。本文将详细介绍 ECMAScript 2015 - 2025 这 10 年间 JavaScript 的主要更新内容。

ECMAScript 2015(ES6)

2015 年发布的 ECMAScript 2015 是 JavaScript 发展史上的一个重大版本,引入了许多新的语法特性和功能,极大地提升了语言的表达能力和开发效率。

  • 块级 作用域:引入了 letconst 关键字,使我们能够在块级作用域中声明变量和常量,避免了变量提升和命名冲突的问题。例如:
{
  let x = 10;
  const y = 20;
  console.log(x); // 输出:10
  console.log(y); // 输出:20
}
console.log(x); // 报错:x 未定义
console.log(y); // 报错:y 未定义
  • 箭头函数:一种更简洁的函数定义方式,使用箭头(=>)来定义函数表达式,自动绑定了上下文,省略了 function 关键字,并且具有隐式返回值的特性。例如:
const square = x => x * x;
console.log(square(5)); // 输出:25
  • 默认参数:允许为函数参数设置默认值,简化函数的调用,避免传递 undefinednull 的情况。例如:
function greet(name = 'Anonymous') {
  console.log(`Hello, ${name}!`);
}
greet(); // 输出:Hello, Anonymous!
greet('John'); // 输出:Hello, John!
  • 扩展运算符:通过使用扩展运算符 ...,可以方便地展开数组或对象,进行合并、复制或解构等操作。例如:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const mergedArray = [...arr1, ...arr2];
console.log(mergedArray); // 输出:[1, 2, 3, 4, 5, 6]
  • 解构赋值:从数组或对象中提取值并赋给变量或常量的语法,使代码更加简洁易读。例如:
const person = {
  name: 'John',
  age: 30,
  address: {
    city: 'New York',
    country: 'USA'
  }
};
const { name, age, address: { city } } = person;
console.log(name); // 输出:John
console.log(age); // 输出:30
console.log(city); // 输出:New York
  • 模板 字面量:使用反引号(`)来创建多行字符串,并支持在字符串中插入变量或表达式,使字符串的拼接更加直观和简洁。例如:
const name = 'John';
const age = 30;
const message = `My name is ${name} and I'm ${age} years old.`;
console.log(message); // 输出:My name is John and I'm 30 years old.
  • 类和模块:引入了 class 关键字,使面向对象编程更加直观和易用,通过 extends 关键字实现类的继承。同时引入了 importexport 关键字,用于模块的导入和导出,使代码的组织和模块化更加清晰和可维护。例如:
// 模块导出
export class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(`Hello, ${this.name}!`);
  }
}
// 模块导入
import { Person } from './person';
const john = new Person('John');
john.sayHello(); // 输出:Hello, John!
  • Promise:一种处理异步操作的方式,解决了传统回调函数中的回调地狱问题,使异步代码的编写更加优雅和可维护。例如:
function fetchData() {
  return new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      const data = 'Hello, Promise!';
      resolve(data); // 成功时调用 resolve
      // reject(new Error('Something went wrong!')); // 失败时调用 reject
    }, 2000);
  });
}
fetchData()
 .then(data => {
    console.log(data); // 输出:Hello, Promise!
  })
 .catch(error => {
    console.error(error); // 输出:Error: Something went wrong!
  });
  • 迭代器和生成器:引入了迭代器和生成器的概念,使遍历操作更加灵活和简洁。迭代器提供了一种统一的遍历接口,而生成器则是一种特殊的函数。

ECMAScript 2016(ES7)

2016 年发布的 ECMAScript 2016 引入了一些小的更新,进一步增强了语言的功能。

  • Array.prototype.includes() :用于判断数组是否包含某个元素,代替了 indexOf()。例如:
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
  • 指数操作符(Exponentiation Operator) :新增 ** 操作符,作为替代 Math.pow() 的简写。例如:
console.log(2 ** 3); // 8

ECMAScript 2017(ES8)

2017 年发布的 ECMAScript 2017 继续完善和扩展 JavaScript 的功能,特别是在异步编程和对象操作方面。

  • async await:引入了 async 函数和 await 关键字,用于简化异步代码的写法,避免回调地狱。例如:
async function fetchData() {
  let response = await fetch('https://api.example.com');
  let data = await response.json();
  console.log(data);
}
fetchData();
  • Object.entries() Object.values()Object.entries() 返回一个对象的键值对数组,Object.values() 返回对象的所有值组成的数组。例如:
const obj = { a: 1, b: 2 };
console.log(Object.entries(obj)); // [['a', 1], ['b', 2]]
console.log(Object.values(obj)); // [1, 2]
  • String.prototype.padStart() String.prototype.padEnd() :用于给字符串补充指定字符,使其达到指定长度。例如:
'5'.padStart(2, '0'); // '05'
'5'.padEnd(3, '0');   // '500'
  • SharedArrayBuffer 和 Atomics:为多线程开发提供了支持,用于在 Web Workers 中进行共享内存操作。

ECMAScript 2018(ES9)

2018 年发布的 ECMAScript 2018 为 JavaScript 带来了一些新的特性,特别是在异步编程和正则表达式方面。

  • 异步迭代( async iterators) :新增 for-await-of 循环,允许在异步迭代器上进行迭代。例如:
async function fetchData() {
  const asyncIterable = getAsyncIterable(); // 假设返回一个异步可迭代对象
  for await (const item of asyncIterable) {
    console.log(item);
  }
}
  • Rest/Spread 属性:允许对象解构时使用剩余参数(...)或展开运算符(...)来收集或展开对象属性。例如:
const obj = { a: 1, b: 2, c: 3 };
const { a, ...rest } = obj; // { b: 2, c: 3 }
  • RegExp 的改进

    • RegExp.prototype.flags:新增 flags 属性,返回正则表达式的标志。
    • RegExpdotAll 标志:允许点号(.)匹配所有字符,包括换行符。例如:
const regex = /./s;
console.log(regex.test('\n')); // true

ECMAScript 2019(ES10)

2019 年发布的 ECMAScript 2019 对数组、对象和字符串操作进行了优化和改进。

  • Array.prototype.flat() Array.prototype.flatMap()flat() 将多维数组扁平化为一维数组,flatMap() 首先使用映射函数映射每个元素,然后将结果扁平化。例如:
[1, [2, [3, 4]]].flat(2); // [1, 2, 3, 4]
[1, 2, 3].flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
  • Object.fromEntries() :与 Object.entries() 相反,允许将一个键值对的数组转换成一个对象。例如:
const entries = [['a', 1], ['b', 2]];
const obj = Object.fromEntries(entries);
console.log(obj); // { a: 1, b: 2 }
  • String.prototype.trimStart() String.prototype.trimEnd() :用于修剪字符串的开始或结尾的空格。例如:
'  hello '.trimStart(); // 'hello '
'  hello '.trimEnd();   // '  hello'
  • Symbol.prototype.description:支持直接获取 Symbol 的描述。例如:
const sym = Symbol("foo");
console.log(sym.description); // "foo"
  • try…catch 可省略 err:在某些情况下,可以省略 try…catch 中的错误参数。例如:
try {
  // 可能会抛出错误的代码
} catch {
  // 处理错误的代码
}

ECMAScript 2020(ES11)

2020 年发布的 ECMAScript 2020 引入了一些重要的新特性,提升了 JavaScript 的编写效率和灵活性。

  • 动态 import() :允许在运行时动态导入模块,通过按需加载依赖项来帮助减少分发包的大小。例如:
const mod = figure.kind === "rectangle" ? "rectangle.js" : "circle.js";
const { calcSquare } = await import(mod);
console.log(calcSquare(figure));
  • 空值合并运算符( ?? :仅当初始值为 nullundefined 时才求右手边的值,解决了使用短循环设置默认值的缺陷。例如:
const initialVal = 0;
const myVar = initialVal ?? 10; // => 0
  • 可选链操作符( ?. :在处理嵌套对象和检查可能的 undefined 时使代码更短。例如:
const user = { name: "John" };
const city = user?.address?.city ?? "Not Set";
  • BigInt:一个新的对象,代表大于 Number.MAX_SAFE_INTEGER 的数字,适用于某些数学应用程序和机器学习。例如:
const bigNum = 123456789012345678901234567890n;
  • Promise.allSettled() :当所有的 Promise 完成时,即成为兑现或被拒绝,它都会解决,解析为一个数组,其中包含 Promise 的状态及其所解析的内容(或错误)。例如:
const promises = [Promise.resolve(1), Promise.reject(new Error('Error'))];
Promise.allSettled(promises).then(results => console.log(results));
  • globalThis:消除了不同环境下全局对象的差异,始终可以在 globalThis 不关心所处的上下文中进行引用。

ECMAScript 2021(ES12)

2021 年发布的 ECMAScript 2021 为 JavaScript 带来了一些实用的新特性。

  • 逻辑赋值运算符( &&= ||= ??= :将逻辑运算符与赋值运算符进行结合,为变量或属性设置默认值。例如:
let x = 1;
x &&= 10; // 等同于 x && (x = 10);
  • 数字分隔符:允许 JavaScript 的数值使用下划线(_)作为分隔符,提高长数字的可读性。例如:
const budget = 1_000_000_000_000;
  • String.prototype.replaceAll() :可以一次性替换所有匹配的子字符串,简化了字符串替换操作。例如:
'aabbcc'.replaceAll('b', '_'); // 'aa__cc'
  • Promise.any() AggregateErrorPromise.any() 返回一个 Promise,当其中一个 Promise 解决时,它就会解决;AggregateError 是一种新的错误类型,用于同时表示多个错误。例如:
Promise.any([Promise.resolve(1), Promise.reject(new Error('Error'))]).then(result => console.log(result));
  • WeakRef FinalizationRegistryWeakRef 用于直接创建对象的弱引用,FinalizationRegistry 用来指定目标对象被垃圾回收机制清除以后所要执行的回调函数。例如:
const target = {};
const wr = new WeakRef(target);
const registry = new FinalizationRegistry(heldValue => {
  console.log(`${heldValue} 对象已被垃圾回收`);
});
registry.register(target, 'target');

ECMAScript 2022(ES13)

2022 年发布的 ECMAScript 2022 为开发者提供了多项新特性,使语言本身变得更加强大与易用。

  • 顶层 await:允许在模块的顶层直接使用 await,简化了依赖异步数据的模块的代码结构。例如:
const response = await fetch('https://api.example.com/data');
const data = await response.json();
export default data;
  • 类静态初始化块:为类的静态属性初始化提供一个独立的代码块,增强了类定义的可读性与可维护性。例如:
class ConfigManager {
  static config = {};
  static {
    try {
      // 模拟从外部文件或远程服务加载配置数据
      ConfigManager.config = loadConfig('./config.json');
    } catch (error) {
      console.error('配置加载失败', error);
      ConfigManager.config = { mode: 'default' };
    }
  }
}
  • Array.prototype.at() :允许通过传入正数或负数索引,便捷地获取数组中相应位置的元素。例如:
const numbers = [10, 20, 30, 40, 50];
const lastNumber = numbers.at(-1);
console.log(lastNumber); // 输出 50
  • 错误原因( Error Cause :允许在创建错误对象时传递一个描述错误根源的参数,帮助开发者在调试过程中快速定位问题。例如:
function fetchData(url) {
  try {
    // 模拟网络请求
    throw new Error('请求超时');
  } catch (error) {
    throw new Error('数据获取失败', { cause: error });
  }
}
try {
  fetchData('https://api.example.com/data');
} catch (err) {
  console.error('错误原因:', err.cause);
}
  • 正则表达式匹配索引( RegExp Match Indices :提供了正则表达式匹配结果的索引信息。
  • Object.hasOwn 方法:用于检查对象是否具有指定的自有属性。例如:
const obj = { name: 'John' };
console.log(Object.hasOwn(obj, 'name')); // true

ECMAScript 2023(ES14)

2023 年发布的 ECMAScript 2023 引入了一些小的改进和新特性,提升了语言的表达力和实用性。

  • Array.prototype.findLast() Array.prototype.findLastIndex() :从数组末尾开始查找元素或索引。例如:
const arr = [1, 2, 3, 4];
console.log(arr.findLast((x) => x % 2 === 0)); // 4
console.log(arr.findLastIndex((x) => x % 2 === 0)); // 3
  • Hashbang 语法支持:支持在脚本文件开头使用 #!/usr/bin/env node 指定解释器。例如:
#!/usr/bin/env node
console.log("Hello, World!");
  • Symbol.prototype.description 改进:支持直接获取 Symbol 的描述。例如:
const sym = Symbol("foo");
console.log(sym.description); // "foo"
  • 更快的数组操作:优化了数组方法的实现,如 mapfilterreduce 等。
  • 更高效的垃圾回收:减少了内存占用。

ECMAScript 2024(ES15)

2024 年 6 月 26 日,第 127 届 ECMA 大会正式批准了 ECMAScript 2024 语言规范,带来了一系列令人兴奋的功能。

  • Group By 分组Map.groupBy() 将可迭代对象分组为一个新的 MapObject.groupBy() 生成一个对象。例如:
Map.groupBy([0, -5, 3, -4, 8, 9], x => Math.sign(x));
Object.groupBy([0, -5, 3, -4, 8, 9], x => Math.sign(x));
  • Promise.withResolvers() :提供了一种创建 Promise 的新方法。例如:
const { promise, resolve, reject } = Promise.withResolvers();
  • 正则表达式标志 /v:是 u 标志的“升级”,可启用更多与 Unicode 相关的功能。例如:
/^[\w--[a-g]]$/v.test('a'); // false
/^[\w--[a-g]]$/v.test('i'); // true
  • ArrayBuffers SharedArrayBuffers 的新功能

    • ArrayBuffers 就地调整大小:可增大也可以减小,但不允许超过预先定义的 maxByteLength。例如:
const buf = new ArrayBuffer(2, { maxByteLength: 32 });
buf.resize(12);
  • ArrayBuffers.transfer() 可转移:对外提供了一个 transfer 函数调用,更加方便。例如:
const original = new ArrayBuffer(16);
const transferred = original.transfer();
  • SharedArrayBuffers 可以调整大小,但只能增长而不能缩小,不可转移。

  • 新增了两个确保字符串格式正确的函数

    • String.prototype.isWellFormed():测试一个字符串是否是格式正确的(即不包含单独代理项)。例如:
const strings = [
  // 单独的前导代理
  "ab\uD800",
  "ab\uD800c",
  // 单独的后尾代理
  "\uDFFFab",
  "c\uDFFFab",
  // 格式正确
  "abc",
  "ab\uD83D\uDE04c"
];
for (const str of strings) {
  console.log(str.isWellFormed());
}
  • String.prototype.toWellFormed():返回一个字符串,其中该字符串的所有单独代理项都被替换为 Unicode 替换字符 U+FFFD。例如:
const str = "ab\uD800";
console.log(str.toWellFormed());

ECMAScript 2025(ES16)

2025 年,ECMAScript 再次带来多项重磅更新。

  • 更智能的 异步处理 Promise.try:同步错误自动转化为 Promise 拒绝,避免嵌套语句,使异步代码与同步代码异常处理统一化,执行时序更符合直觉。例如:
function fetchData() {
  if (Math.random() < 0.5) throw new Error('同步错误');
  return Promise.resolve('数据');
}
Promise.try(fetchData)
 .then(data => console.log(data))
 .catch(err => console.error('统一捕获:', err));
  • 集合运算: Set 方法增强:新增 intersection(交集)、difference(差集)、union(并集)方法。例如:
const devs = new Set(['Alice', 'Bob']);
const seniors = new Set(['Alice', 'Charlie']);
// 交集:同时具备开发与资深身份
devs.intersection(seniors); // Set {'Alice'}
// 差集:普通开发者
devs.difference(seniors);   // Set {'Bob'}
// 并集:所有相关人员
devs.union(seniors);        // Set {'Alice','Bob','Charlie'}
  • 正则表达式

    • 重复命名捕获组:不同分支可复用相同组名,直接通过统一字段访问数据。例如:
// ES2025 新方案:统一组名
const DATE_REGEX = /^
  (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
  |
  (?<month>\d{2})/(?<day>\d{2})/(?<year>\d{4})
$/;
const { year, month, day } = match.groups;  // 自动匹配对应分支的组
  • 正则表达式局部修饰符:精准控制匹配规则。例如:
// 仅对部分模式启用忽略大小写
const re = /HELLO(?i: World)/;
re.test('HELLO world'); // true(World 部分不区分大小写)
  • 其他重要更新

    • 延迟模块加载 (Deferred Module Evaluation) :优化大型应用启动性能,模块在声明时即开始加载(与主线程并行),但不执行模块代码,代码的执行推迟到首次访问其导出成员时触发。例如:
// 按需加载重型模块
defer import heavyModule from './heavy.js';
button.onclick = async () => {
  await heavyModule.run(); // 点击时才加载
};

结论

从 ECMAScript 2015 到 2025 这 10 年间,JavaScript 不断发展和演进,每个版本都引入了许多新的特性和功能,这些更新使得 JavaScript 更加现代化、强大和易用。开发者可以利用这些新特性来提高开发效率、编写更简洁和可维护的代码,同时也为 Web 开发带来了更多的可能性。随着技术的不断发展,相信 JavaScript 会继续保持活力,为开发者带来更多的惊喜。