引言
JavaScript 作为一门广泛应用于 Web 开发的编程语言,其发展历程中经历了多次重要的版本更新。自 2015 年起,ECMAScript 采用了每年发布一个新版本的策略,不断为 JavaScript 注入新的活力和功能。本文将详细介绍 ECMAScript 2015 - 2025 这 10 年间 JavaScript 的主要更新内容。
ECMAScript 2015(ES6)
2015 年发布的 ECMAScript 2015 是 JavaScript 发展史上的一个重大版本,引入了许多新的语法特性和功能,极大地提升了语言的表达能力和开发效率。
- 块级 作用域:引入了
let和const关键字,使我们能够在块级作用域中声明变量和常量,避免了变量提升和命名冲突的问题。例如:
{
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
- 默认参数:允许为函数参数设置默认值,简化函数的调用,避免传递
undefined或null的情况。例如:
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关键字实现类的继承。同时引入了import和export关键字,用于模块的导入和导出,使代码的组织和模块化更加清晰和可维护。例如:
// 模块导出
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 带来了一些新的特性,特别是在异步编程和正则表达式方面。
- 异步迭代(
asynciterators) :新增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属性,返回正则表达式的标志。RegExp的dotAll标志:允许点号(.)匹配所有字符,包括换行符。例如:
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));
- 空值合并运算符(
??) :仅当初始值为null或undefined时才求右手边的值,解决了使用短循环设置默认值的缺陷。例如:
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()和AggregateError:Promise.any()返回一个Promise,当其中一个Promise解决时,它就会解决;AggregateError是一种新的错误类型,用于同时表示多个错误。例如:
Promise.any([Promise.resolve(1), Promise.reject(new Error('Error'))]).then(result => console.log(result));
WeakRef和FinalizationRegistry:WeakRef用于直接创建对象的弱引用,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"
- 更快的数组操作:优化了数组方法的实现,如
map、filter、reduce等。 - 更高效的垃圾回收:减少了内存占用。
ECMAScript 2024(ES15)
2024 年 6 月 26 日,第 127 届 ECMA 大会正式批准了 ECMAScript 2024 语言规范,带来了一系列令人兴奋的功能。
Group By分组:Map.groupBy()将可迭代对象分组为一个新的Map,Object.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 会继续保持活力,为开发者带来更多的惊喜。