JS随笔:ES6+特性与模块化实践
本篇是「JS随笔」系列中的 ES6+ 特性与模块化篇,从历史背景与版本演进切入,系统梳理 ES6 及后续版本(ES6+)的重要语言特性与模块化实践,覆盖
let/const、箭头函数、默认参数与模板字符串、展开与解构、类与继承、模块的导入导出、集合类型等。
原文地址
历史背景
ES6 的历史背景可以追溯到 1996 年,当时 JS 的创造者 Netscape 公司决定将 JS 提交给国际标准化组织 ECMA。次年,ECMA 发布了 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript。
1997 至 1999 年,ECMAScript 经历了 2.0 和 3.0 两个版本迭代,其中 3.0 在业界广泛支持,奠定了语言基本语法。2000 年开始酝酿的 ECMAScript 4.0 因过于激进未通过,其大量内容被 ES6 继承。2008 年 Harmony 代号启动,ES5 在 2009 年发布。2013 年 ES6 草案冻结,2015 年正式通过,被称为 ES2015。此后规范按年度持续迭代。
应用场景
- 模块化编程:使用 ES6 模块组织项目结构与依赖。
- 异步编程 :
Promise、async/await简化异步控制流与错误处理。 - 面向对象 :
class与extends提供更清晰的封装与继承。 - 函数式编程 :箭头函数与数组高阶函数(
map、filter、reduce)。 - 语法简化:模板字符串、参数解构、展开运算符等。
版本变动(概览)
自 ES6(ES2015)起每年发布新版本,加入新特性:
- ES7 (2016) :
Array.prototype.includes、指数运算符** - ES8 (2017) :
async/await、Object.values、Object.entries、字符串填充 - ES9 (2018) :异步迭代器、
for-await-of、Promise.finally、正则命名捕获 - ES10 (2019) :
Array.prototype.flat/flatMap、Object.fromEntries、字符串前后裁剪 - ES11 (2020) :可选链
?.、空值合并?? - ES12 (2021) :逻辑赋值
||= / &&= / ??=、Promise.allSettled
年度更新(ES2025/ES2026)
- Iterator Helpers(ES2025) :迭代器原生支持
.map()、.filter()、.take()等惰性管道方法,适合数据流与大集合处理。[TC39/ES2025、Saeloun] - Set 扩展(ES2025) :集合数学运算
union、intersection、difference、symmetricDifference以及关系判定isSubsetOf、isSupersetOf、isDisjointFrom。[InfoWorld、Saeloun] - JSON 模块(ES2025) :允许直接
import data from './data.json',减少构建与手动解析。[InfoWorld] - Temporal(ES2026 预计) :现代日期时间 API,替代历史
Date的精度与时区问题。[The New Stack / TC39 草案] - Intl 与模块加载优化(ES2026 预计) :如
Intl.Locale变体与import defer,改进国际化与加载性能。[The New Stack]
新的变量声明方式
let
- 块级作用域,值可重新赋值
- 同一作用域不可重复声明
- 存在暂时性死区
javascript
let age;
age = 30;
console.log(age); // 30
const
- 块级作用域常量,声明时必须初始化,引用不可重新绑定
- 基本类型值不可变;对象/数组引用不可变但内容可变
javascript
const age = 30;
const obj = { key: 'value' };
obj.key = 'newValue';
与 var 的对比
var具函数/全局作用域;可重复声明;存在变量提升与全局污染风险
let/const避免上述问题,推荐优先使用。
实战中,比较常见的一条经验是:
- 默认使用
const,仅在确实需要重新赋值时改用let - 避免在同一作用域混用多个声明同名变量,以减少"阴影变量"导致的逻辑混淆
箭头函数
- 更简洁的函数定义;单表达式可隐式返回
- 无
this、无arguments;不适作构造函数;call/apply/bind无效
javascript
const multiply = (x, y) => x * y;
const double = x => x * 2;
默认参数与模板字符串
默认参数允许在函数声明时为形参提供"兜底值",避免在函数体内部编写大量 name = name || 'xxx' 之类的兼容代码。 需要注意的是,默认值只在实参为 undefined 时才会生效,传入 null 会被视作"有效值"。
模板字符串则通过反引号与 ${} 插值语法,将字符串拼接与表达式求值结合起来, 非常适合用于构建多行文本、日志、SQL/GraphQL 片段等结构化内容。
javascript
function greet(name = 'World') {
console.log(`Hello, ${name}!`);
}
greet();
展开运算符与解构赋值
展开运算符用于在"期望一串元素或键值对"的地方铺开数组或对象, 是构建新数组/对象和实现浅拷贝的利器;解构赋值则提供了从复杂结构中按模式提取字段的语法糖, 大幅减少中间变量与重复访问。
需要牢记两点:
- 展开/解构都是浅层操作,嵌套对象仍然是共享引用
- 解构时可以配合默认值与重命名,提升代码的自文档性
javascript
const arr = [1, 2, 3];
const [a, ...b] = arr; // a=1, b=[2,3]
const obj = { x: 1, y: 2, z: 3 };
const { x, ...y } = obj; // x=1, y={y:2, z:3}
类(class)
声明与静态方法
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}!`);
}
static info() {
console.log('This is a static method.');
}
}
Person.info();
继承与 super
javascript
class Employee extends Person {
constructor(name, age, jobTitle) {
super(name, age);
this.jobTitle = jobTitle;
}
jobDescription() {
Person.info();
console.log(`${this.name} is a ${this.jobTitle}.`);
}
}
getter/setter 与私有
javascript
class PersonX {
get age() { return this._age; }
set age(v) { if (v > 0) this._age = v; }
}
ES6 本身无原生私有字段,可通过 Symbol 或闭包模拟;后续版本已标准化类私有字段(以 # 形式)与静态初始化等能力(属于 ESNext/后续快照)。
模块化
导出
javascript
export function add(x, y) { return x + y; }
export const PI = 3.14;
const tools = {};
export default tools;
导入
javascript
import { add, PI } from './math.js';
import defaultFunction from './utils.js';
import * as MathUtils from './math.js';
MathUtils.add(2, 3);
if (someCondition) {
import('./module.js')
.then(m => m.doSomething())
.catch(err => console.error(err));
}
模块化的好处
- 封装性:避免全局命名污染
- 可维护性:结构清晰、依赖明确
- 可重用性:跨项目复用
- 依赖管理:模块间边界与关系清晰
迭代器与生成器
- 迭代协议 :实现
[Symbol.iterator]返回迭代器对象(含next()) - 生成器 :
function*定义可暂停函数,配合yield逐步产生值
javascript
function* gen() {
yield 1; yield 2; yield 3;
}
[...gen()]; // [1,2,3]
Proxy 与 Reflect
- Proxy:为对象提供拦截器,定义访问/赋值/函数调用等行为
- Reflect :与
Proxy配套的基本操作集合,提升一致性与可预测性
javascript
const target = { x: 1 };
const p = new Proxy(target, {
get(obj, key) { return key in obj ? obj[key] : undefined; }
});
p.x; // 1
类私有字段与静态块(ESNext)
- 私有字段 :使用
#name声明,外部不可访问 - 静态初始化块:在类加载时执行一次的初始化逻辑
javascript
class Counter {
#value = 0;
inc() { this.#value++; }
get value() { return this.#value; }
static {
// 静态初始化
}
}
顶层 await 与 Import Assertions
- 顶层 await :在模块顶层使用
await,简化初始化流程(仅模块可用) - Import Assertions :为导入资源提供类型断言(如
assert { type: 'json' })
javascript
const data = await fetch('/config.json').then(r => r.json());
import config from './config.json' assert { type: 'json' };
异步与 Promise(简述)
javascript
const myPromise = new Promise((resolve, reject) => {
// ...
resolve('value');
});
myPromise.then(v => { /* ... */ }).catch(err => { /* ... */ });
链式调用:
javascript
fetch('https://api.example.com/data')
.then(r => r.json())
.then(data => { /* ... */ })
.catch(err => { /* ... */ });
静态方法:
javascript
Promise.all([p1, p2, p3]).then(results => { /* ... */ });
Promise.race([p1, p2]).then(result => { /* ... */ });
async/await:
javascript
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
} catch (error) {
// ...
}
}
Map / WeakMap
javascript
const map = new Map();
map.set(key, value);
map.get(key);
map.has(key);
map.delete(key);
map.forEach((value, key) => { /* ... */ });
WeakMap:键必须是对象;弱引用;不可遍历;无 size。
Set / WeakSet
javascript
const mySet = new Set();
mySet.add(1);
mySet.add('text');
mySet.add({ name: 'Tom' });
mySet.has(1);
mySet.size;
mySet.delete('text');
mySet.forEach(v => console.log(v));
const uniqueNumbers = [...new Set([1,2,2,3,4,4,5])];
WeakSet:仅存对象引用;弱引用;不可遍历。
Symbol 与私有属性
Symbol 提供了一种"永不冲突"的键类型,即便是描述相同的 Symbol('desc') 也彼此不相等。 这非常适合作为库或框架内部的扩展点键名,避免与业务代码在对象属性上踩踏。
在"私有属性"场景中,Symbol 更像是一种"弱隐藏"机制:外部若持有该 Symbol 仍然可以访问对应属性, 但在常规枚举(如 Object.keys/for...in)中不会被轻易遍历出来。
对于真正需要强封装的类内部状态,ES 提供了 #private 字段语法; 而 Symbol 更适合用在元数据、扩展协议、跨模块通信这些场景中,与公开 API 共存。
javascript
const mySymbol = Symbol('mySymbol');
const obj = { [mySymbol]: 'Only one key' };
ES2025/ES2026 详解与示例
Iterator Helpers(ES2025)
- 在迭代器上直接使用
.map/.filter/.take/.drop/.flatMap等方法 - 惰性处理,适合大集合与流式数据
javascript
const it = [1,2,3,4,5].values();
const pipeline = it.filter(x => x % 2).map(x => x * 10).take(3);
for (const v of pipeline) { /* 1->10, 3->30, 5->50 */ }
Set 扩展(ES2025)
- 集合运算:
union/intersection/difference/symmetricDifference - 关系判断:
isSubsetOf/isSupersetOf/isDisjointFrom
javascript
const A = new Set([1,2,3]);
const B = new Set([3,4,5]);
A.union(B); // Set {1,2,3,4,5}
A.intersection(B); // Set {3}
A.difference(B); // Set {1,2}
A.symmetricDifference(B); // Set {1,2,4,5}
A.isSubsetOf(new Set([1,2,3,4])); // true
JSON 模块(ES2025)
- 直接导入 JSON 作为模块,减少手动解析与构建步骤
- 可配合 Import Assertions 明确类型
javascript
import config from './config.json' assert { type: 'json' };
console.log(config.title);
Temporal(ES2026 预计)
- 现代日期时间 API:精确、时区安全
- 与模块初始化/调度结合,避免
Date的历史坑
javascript
const now = Temporal.Now.zonedDateTimeISO('Asia/Shanghai');
const deadline = now.add({ hours: 1 });
import defer(ES2026 预计)
- 延迟部分导入的求值,用于非关键模块的初始化
javascript
import defer analytics from './analytics.js';
// 完成主业务初始化后,再进行分析模块启动
queueMicrotask(() => analytics.start());
实践建议(ES6+ 到 ES2026)
- 优先模块化与顶层 await,简化初始化流程
- 在数据密集场景使用 Iterator Helpers,避免中间数组
- 使用 Set 扩展实现集合运算,替换手写版本以提高可读性
- 通过 JSON 模块直接消费配置与数据快照
- 引入 Temporal 做时间计算与时区管理
- 针对非关键路径采用 import defer 与空闲调度策略
生成器与异步迭代(进阶)
- 将生成器与
for...of结合打造数据管道 - 异步迭代器支持
for await...of逐块拉取数据
javascript
function* range(n) { for (let i = 0; i < n; i++) yield i; }
const squares = (function* (iter) {
for (const x of iter) yield x * x;
})(range(5));
[...squares]; // [0,1,4,9,16]
javascript
async function* fetchPages(ids) {
for (const id of ids) {
const res = await fetch(`/api/${id}`);
yield res.json();
}
}
Proxy/Reflect 高级用法
- 数据校验与只读视图
- 变更跟踪与调试
javascript
const model = { x: 1 };
const ro = new Proxy(model, {
set() { throw new Error('read-only'); },
get(obj, key) { return Reflect.get(obj, key); }
});
模块模式与实践
- 入口(index)统一导出子模块
- 动态导入配合路由与懒加载
javascript
// src/index.js
export * from './math.js';
export * from './string.js';
javascript
// 动态导入
const route = 'settings';
const page = await import(`./pages/${route}.js`);
page.render();
类装配与组合技巧
- 通过静态工厂封装构造复杂对象
- 组合优于继承,使用委托与小对象拼装
javascript
class HttpClient {
static fromToken(token) {
return new HttpClient({ headers: { Authorization: `Bearer ${token}` } });
}
constructor(opts) { this.opts = opts; }
get(url) { return fetch(url, this.opts); }
}