0%

模块与类

目标:掌握 JavaScript 模块化(CommonJS 和 ES6 模块)、类的使用、继承机制,以及生成器和迭代器的应用。

目录

  1. 模块化
  2. 类与构造
  3. Generator 函数与迭代器
  4. 实战应用场景
  5. 代码片段速查
  6. 常见问题与解决方案
  7. 速记与总结

1. 模块化

模块化是将代码组织成独立、可复用的单元的方式。JavaScript 主要有两种模块系统:CommonJS(Node.js)和 ES6 模块(ESM)。

1.1 CommonJS(Node.js)

CommonJS 是 Node.js 的模块系统,使用 requiremodule.exports

1.1.1 基本语法

导出模块

1
2
3
4
5
6
7
8
9
10
11
12
13
// math.js
// 方式1:使用 module.exports
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};

// 方式2:使用 exports(module.exports 的简写)
exports.multiply = (a, b) => a * b;
exports.divide = (a, b) => a / b;

// 注意:不能直接给 exports 赋值
// exports = { ... }; // ❌ 错误,不会生效

导入模块

1
2
3
4
5
6
7
// app.js
// 导入整个模块
const math = require('./math.js');
console.log(math.add(1, 2)); // 3

// 导入时可以省略 .js 扩展名
const math2 = require('./math');

1.1.2 加载特性

1. 同步加载

CommonJS 是同步加载的,模块会在 require 时立即执行。

1
2
3
4
5
6
7
8
9
10
11
12
// module.js
console.log('模块被加载了');
module.exports = { value: 42 };

// app.js
console.log('开始加载模块');
const mod = require('./module');
console.log('模块加载完成');
// 输出:
// 开始加载模块
// 模块被加载了
// 模块加载完成

2. 模块缓存

Node.js 会缓存已加载的模块,同一个模块只会执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
// counter.js
let count = 0;
module.exports = {
increment: () => ++count,
getCount: () => count
};

// app.js
const counter1 = require('./counter');
const counter2 = require('./counter');

counter1.increment();
console.log(counter2.getCount()); // 1(共享同一个模块实例)

3. 按需执行

模块代码在第一次 require 时执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// lazy.js
console.log('lazy 模块执行了');
module.exports = 'lazy';

// app.js
console.log('app 开始');
// 此时 lazy.js 还没有执行

function loadLazy() {
const lazy = require('./lazy'); // 此时才执行
return lazy;
}

console.log('app 继续');
const result = loadLazy();

1.1.3 值拷贝 vs 引用

基本类型:值拷贝

1
2
3
4
5
6
7
// counter.js
let count = 0;
module.exports = count; // 导出基本类型

// app.js
const count = require('./counter');
count = 10; // 不会影响原模块的 count

对象:引用

1
2
3
4
5
6
7
8
// state.js
const state = { count: 0 };
module.exports = state;

// app.js
const state = require('./state');
state.count = 10; // 会影响原模块的 state
console.log(state.count); // 10

1.1.4 循环依赖

CommonJS 的循环依赖可能导致未完全初始化的导出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// a.js
console.log('a.js 开始');
const b = require('./b');
console.log('a.js 中 b:', b);
module.exports = { name: 'a' };

// b.js
console.log('b.js 开始');
const a = require('./a');
console.log('b.js 中 a:', a); // 可能是 {}(未完全初始化)
module.exports = { name: 'b' };

// 执行顺序:
// a.js 开始
// b.js 开始
// b.js 中 a: {}(a.js 还没执行完)
// a.js 中 b: { name: 'b' }

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// a.js
console.log('a.js 开始');
let b;
module.exports = {
name: 'a',
getB: () => {
if (!b) {
b = require('./b');
}
return b;
}
};

// b.js
console.log('b.js 开始');
let a;
module.exports = {
name: 'b',
getA: () => {
if (!a) {
a = require('./a');
}
return a;
}
};

1.1.5 使用场景

  • Node.js 传统模块系统
  • 打包工具(如 Webpack)会将 ESM 转换为 CommonJS 以兼容旧环境
  • 需要同步加载的场景

1.2 ES6 模块(ESM)

ES6 模块是 JavaScript 的官方模块系统,使用 importexport

1.2.1 基本语法

导出模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// math.js
// 方式1:命名导出(导出时声明)
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// 方式2:命名导出(先声明后导出)
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;
export { multiply, divide };

// 方式3:默认导出(每个模块只能有一个)
export default function calculate(a, b, op) {
return op(a, b);
}

// 方式4:混合导出
export const PI = 3.14159;
export default class Calculator {}

导入模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// app.js
// 方式1:命名导入
import { add, subtract } from './math.js';

// 方式2:命名导入(使用别名)
import { add as sum, subtract as sub } from './math.js';

// 方式3:导入所有命名导出
import * as math from './math.js';
console.log(math.add(1, 2));

// 方式4:默认导入
import calculate from './math.js';

// 方式5:混合导入
import calculate, { add, subtract } from './math.js';

// 方式6:只导入模块(执行副作用)
import './init.js'; // 执行 init.js,但不导入任何内容

1.2.2 加载特性

1. 静态分析

ES6 模块在编译期就能确定依赖关系,可以进行静态分析和优化。

1
2
3
4
5
6
// 这些导入语句必须在顶层,不能动态生成
import { add } from './math.js'; // ✅ 正确

// if (condition) {
// import { add } from './math.js'; // ❌ 错误
// }

2. 严格模式

ES6 模块默认处于严格模式。

1
2
3
// 自动启用严格模式
export const value = 42;
// 不需要 'use strict'

3. 顶层 this 是 undefined

1
2
3
4
// module.js
console.log(this); // undefined(不是 global 或 window)

export const value = 42;

1.2.3 Live Binding(活绑定)

ES6 模块导出的是绑定,不是值的拷贝。导出变量的变化会被导入方感知。

1
2
3
4
5
6
7
8
9
10
11
12
// counter.js
export let count = 0;
export function increment() {
count++;
}

// app.js
import { count, increment } from './counter.js';

console.log(count); // 0
increment();
console.log(count); // 1(值已经改变)

与 CommonJS 的对比

1
2
3
4
5
6
7
8
9
10
// CommonJS(值拷贝)
// counter.js
let count = 0;
module.exports = { count, increment: () => count++ };

// app.js
const { count, increment } = require('./counter');
console.log(count); // 0
increment();
console.log(count); // 0(值没有改变,因为是拷贝)

1.2.4 动态导入

使用 import() 函数可以动态导入模块,返回 Promise。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 动态导入
async function loadModule() {
const math = await import('./math.js');
console.log(math.add(1, 2));
}

// 条件导入
if (condition) {
const module = await import('./module.js');
}

// 懒加载
button.addEventListener('click', async () => {
const { showDialog } = await import('./dialog.js');
showDialog();
});

动态导入的应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 路由懒加载
const routes = {
'/home': () => import('./pages/Home.js'),
'/about': () => import('./pages/About.js')
};

// 2. 按需加载大型库
async function processImage(image) {
const imageProcessor = await import('./imageProcessor.js');
return imageProcessor.process(image);
}

// 3. 条件加载
async function loadPolyfill() {
if (!window.IntersectionObserver) {
await import('./polyfills/intersection-observer.js');
}
}

1.2.5 循环依赖

ES6 模块的循环依赖处理比 CommonJS 更好,因为使用 live binding。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// a.js
import { b } from './b.js';
export const a = 'a';
console.log('a.js 中 b:', b); // 可能是 undefined(如果 b.js 还没执行完)

// b.js
import { a } from './a.js';
export const b = 'b';
console.log('b.js 中 a:', a); // 可能是 undefined

// 执行顺序:
// a.js 开始执行
// 遇到 import,暂停执行 a.js,开始执行 b.js
// b.js 开始执行
// 遇到 import,发现 a.js 正在执行,使用临时绑定
// b.js 执行完,b 的值确定
// 回到 a.js,a 的值确定

最佳实践:避免循环依赖

1
2
3
4
5
6
7
8
9
10
11
// 更好的设计:提取公共模块
// common.js
export const shared = {};

// a.js
import { shared } from './common.js';
export const a = 'a';

// b.js
import { shared } from './common.js';
export const b = 'b';

1.2.6 浏览器中的使用

1. 使用 <script type="module">

1
2
3
4
<script type="module">
import { add } from './math.js';
console.log(add(1, 2));
</script>

2. 特性

  • 延迟执行:模块脚本默认 defer,在 DOM 解析完成后执行
  • CORS:模块必须通过 HTTP(S) 加载,不能使用 file:// 协议
  • 严格模式:自动启用严格模式
  • 作用域:每个模块有自己的作用域

3. import.meta

import.meta 提供模块的元信息。

1
2
3
4
5
6
7
// 获取当前模块的 URL
console.log(import.meta.url);

// 在 Node.js 中
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
console.log(__filename);

1.3 模块作用域与默认导出

1.3.1 模块作用域

每个模块都有自己的作用域,不会污染全局。

1
2
3
4
5
6
7
8
// module.js
const privateVar = 'private'; // 模块私有,外部无法访问
export const publicVar = 'public';

// app.js
import { publicVar } from './module.js';
console.log(publicVar); // 'public'
// console.log(privateVar); // ❌ 错误:未定义

1.3.2 默认导出

特点

  1. 每个模块只能有一个默认导出
1
2
3
// module.js
export default function() {} // ✅ 正确
// export default class {} // ❌ 错误:不能有两个默认导出
  1. 导入时可以自定义名称
1
2
3
4
5
6
7
// module.js
export default function init() {}

// app.js
import initApp from './module.js'; // ✅ 可以改名
import initialize from './module.js'; // ✅ 也可以
import anyName from './module.js'; // ✅ 任何名字都可以
  1. 可以与命名导出混合
1
2
3
4
5
6
// module.js
export default class User {}
export const VERSION = '1.0';

// app.js
import User, { VERSION } from './module.js';

1.3.3 命名导出

特点

  1. 可以有多个命名导出
1
2
3
4
5
// module.js
export const a = 1;
export const b = 2;
export function c() {}
export class D {}
  1. 导入时必须使用解构
1
2
3
4
// app.js
import { a, b, c, D } from './module.js';
// 或者
import { a as aliasA } from './module.js';
  1. 可以重新导出
1
2
3
4
5
// utils.js
export { a, b } from './module.js';
// 等价于
import { a, b } from './module.js';
export { a, b };

1.4 CommonJS vs ESM 对照

特性 CommonJS ES6 模块
加载时机 运行时解析 编译期解析
执行方式 同步 异步(浏览器)
顶层 this module.exports undefined
导出绑定 值拷贝/对象引用 Live binding
循环依赖 可能导出未初始化内容 使用临时绑定
动态导入 require() import()
严格模式 需要手动启用 默认启用

详细对比示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// CommonJS
// counter.js
let count = 0;
module.exports = { count, increment: () => count++ };

// app.js
const { count, increment } = require('./counter');
console.log(count); // 0
increment();
console.log(count); // 0(值拷贝)

// ES6 模块
// counter.js
export let count = 0;
export function increment() { count++; }

// app.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1(live binding)

2. 类与构造

2.1 class 与 constructor

2.1.1 class 的本质

class 是构造函数和原型的语法糖,本质仍然是基于原型的继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// class 语法
class User {
constructor(name) {
this.name = name;
}
say() {
return `hi ${this.name}`;
}
}

// 等价于
function User(name) {
this.name = name;
}
User.prototype.say = function() {
return `hi ${this.name}`;
};

验证

1
2
3
4
5
6
7
8
9
10
class User {
constructor(name) {
this.name = name;
}
}

const user = new User('Alice');
console.log(user.constructor === User); // true
console.log(user.__proto__ === User.prototype); // true
console.log(typeof User); // 'function'

2.1.2 constructor

constructor 是类的构造函数,用于初始化实例。

1
2
3
4
5
6
7
8
9
10
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}

const user = new User('Alice', 25);
console.log(user.name); // 'Alice'
console.log(user.age); // 25

默认构造函数

如果没有定义 constructor,会有一个默认的空构造函数。

1
2
3
4
5
class Empty {}
// 等价于
class Empty {
constructor() {}
}

2.1.3 类字段(Class Fields)

ES2022 引入了类字段语法,可以直接在类体内定义实例字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User {
// 实例字段
role = 'guest';
count = 0;

constructor(name) {
this.name = name;
}

// 方法
say() {
return `hi ${this.name}`;
}
}

const user = new User('Alice');
console.log(user.role); // 'guest'
console.log(user.name); // 'Alice'

类字段的执行顺序

1
2
3
4
5
6
7
8
9
10
class User {
name = 'default';

constructor(name) {
this.name = name; // 会覆盖类字段
}
}

const user = new User('Alice');
console.log(user.name); // 'Alice'(不是 'default')

私有字段(Private Fields)

1
2
3
4
5
6
7
8
9
10
11
class User {
#privateField = 'private'; // 私有字段

getPrivate() {
return this.#privateField; // 可以访问
}
}

const user = new User();
// console.log(user.#privateField); // ❌ 语法错误
console.log(user.getPrivate()); // 'private'

2.2 静态方法与实例方法

2.2.1 静态方法/属性

静态方法/属性属于类本身,不属于实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Util {
static version = '1.0';

static sum(a, b) {
return a + b;
}

static multiply(a, b) {
return a * b;
}
}

// 通过类调用
console.log(Util.sum(1, 2)); // 3
console.log(Util.version); // '1.0'

// 实例不能调用
const util = new Util();
// util.sum(1, 2); // ❌ TypeError

静态方法的 this

1
2
3
4
5
6
7
class MyClass {
static method() {
console.log(this === MyClass); // true
}
}

MyClass.method();

实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MathUtils {
static PI = 3.14159;

static circleArea(radius) {
return this.PI * radius * radius;
}

static distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
}

console.log(MathUtils.circleArea(5));
console.log(MathUtils.distance(0, 0, 3, 4));

2.2.2 实例方法

实例方法定义在原型上,通过实例调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User {
constructor(name) {
this.name = name;
}

say() {
return `hi ${this.name}`;
}

getName() {
return this.name;
}
}

const user = new User('Alice');
console.log(user.say()); // 'hi Alice'
console.log(user.getName()); // 'Alice'

// 验证:方法在原型上
console.log(user.say === User.prototype.say); // true

2.3 Getter / Setter

Getter 和 Setter 提供对属性的访问控制。

2.3.1 Getter

Getter 用于获取属性值,可以计算派生属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}

get area() {
return this.width * this.height;
}

get perimeter() {
return 2 * (this.width + this.height);
}
}

const rect = new Rectangle(5, 3);
console.log(rect.area); // 15(像属性一样访问)
console.log(rect.perimeter); // 16

2.3.2 Setter

Setter 用于设置属性值,可以进行验证和转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Rectangle {
constructor(width, height) {
this._width = width;
this._height = height;
}

get width() {
return this._width;
}

set width(value) {
if (value <= 0) {
throw new Error('宽度必须大于0');
}
this._width = value;
}

get height() {
return this._height;
}

set height(value) {
if (value <= 0) {
throw new Error('高度必须大于0');
}
this._height = value;
}
}

const rect = new Rectangle(5, 3);
rect.width = 10; // 使用 setter
console.log(rect.width); // 10

// rect.width = -1; // ❌ 抛出错误

2.3.3 最佳实践

1. 避免在 getter 中做副作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ❌ 不好的做法
class Counter {
get value() {
console.log('获取值'); // 副作用
this.count++; // 副作用
return this.count;
}
}

// ✅ 好的做法
class Counter {
get value() {
return this.count; // 只返回值
}

increment() {
this.count++; // 副作用放在方法中
}
}

2. 避免在 getter 中做高成本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ❌ 不好的做法
class DataProcessor {
get processedData() {
return this.rawData.map(/* 复杂计算 */); // 每次访问都计算
}
}

// ✅ 好的做法:使用缓存
class DataProcessor {
get processedData() {
if (!this._cache) {
this._cache = this.rawData.map(/* 复杂计算 */);
}
return this._cache;
}
}

2.4 继承、super 与组合

2.4.1 继承

使用 extends 关键字实现继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal {
constructor(name) {
this.name = name;
}

speak() {
return `${this.name} makes a sound`;
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须先调用 super()
this.breed = breed;
}

speak() {
return `${this.name} barks`;
}
}

const dog = new Dog('旺财', '金毛');
console.log(dog.speak()); // '旺财 barks'
console.log(dog.name); // '旺财'(继承自 Animal)

super 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal {
constructor(name) {
this.name = name;
}

move() {
return 'moving';
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name); // 1. 在构造函数中调用父类构造函数
this.breed = breed;
}

move() {
return super.move() + ' on four legs'; // 2. 在方法中调用父类方法
}
}

重要规则

在派生类的构造函数中,必须先调用 super() 才能使用 this

1
2
3
4
5
6
7
class Child extends Parent {
constructor() {
// this.name = 'test'; // ❌ 错误:在 super() 之前不能使用 this
super();
this.name = 'test'; // ✅ 正确
}
}

2.4.2 方法重写

子类可以重写父类的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal {
speak() {
return 'makes a sound';
}
}

class Dog extends Animal {
speak() {
return 'barks'; // 重写父类方法
}
}

class Cat extends Animal {
speak() {
return super.speak() + ' but meows'; // 调用父类方法并扩展
}
}

2.4.3 组合优先于继承

继承的问题

  • 过深的继承树难以维护
  • 子类与父类强耦合
  • 难以复用部分功能

组合的优势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// ❌ 使用继承(不灵活)
class Bird extends Animal {
fly() {
return 'flying';
}
}

class Dog extends Animal {
// 不能复用 fly 方法
}

// ✅ 使用组合(灵活)
class Flyable {
fly() {
return 'flying';
}
}

class Bird extends Animal {
constructor(name) {
super(name);
this.flyable = new Flyable();
}

fly() {
return this.flyable.fly();
}
}

class Dog extends Animal {
// 不需要飞行能力
}

组合模式示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 能力类
class Swimmable {
swim() {
return 'swimming';
}
}

class Flyable {
fly() {
return 'flying';
}
}

// 动物类
class Animal {
constructor(name) {
this.name = name;
}
}

// 组合使用
class Duck extends Animal {
constructor(name) {
super(name);
this.swimmable = new Swimmable();
this.flyable = new Flyable();
}

swim() {
return this.swimmable.swim();
}

fly() {
return this.flyable.fly();
}
}

2.5 抽象/模板式写法

JavaScript 没有原生的抽象类,但可以通过约定或抛错实现。

2.5.1 使用抛错实现抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Repository {
save() {
throw new Error('save() must be implemented');
}

find(id) {
throw new Error('find() must be implemented');
}
}

class UserRepository extends Repository {
save(user) {
// 实现保存逻辑
console.log('Saving user:', user);
}

find(id) {
// 实现查找逻辑
return { id, name: 'User' };
}
}

2.5.2 使用 Symbol 实现私有抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const SAVE = Symbol('save');

class Repository {
[SAVE]() {
throw new Error('save() must be implemented');
}

save(data) {
return this[SAVE](data);
}
}

class UserRepository extends Repository {
[SAVE](user) {
console.log('Saving user:', user);
}
}

2.5.3 模板方法模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class DataProcessor {
process(data) {
const validated = this.validate(data);
const transformed = this.transform(validated);
return this.save(transformed);
}

validate(data) {
throw new Error('validate() must be implemented');
}

transform(data) {
throw new Error('transform() must be implemented');
}

save(data) {
throw new Error('save() must be implemented');
}
}

class UserProcessor extends DataProcessor {
validate(data) {
if (!data.name) {
throw new Error('Name is required');
}
return data;
}

transform(data) {
return { ...data, processed: true };
}

save(data) {
console.log('Saving:', data);
return data;
}
}

3. Generator 函数与迭代器

3.1 迭代器协议

3.1.1 什么是迭代器?

迭代器是一个对象,它实现了 next() 方法,返回 { value, done }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 自定义迭代器
const iterator = {
data: [1, 2, 3],
index: 0,

next() {
if (this.index < this.data.length) {
return {
value: this.data[this.index++],
done: false
};
}
return { done: true };
}
};

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { done: true }

3.1.2 可迭代对象

可迭代对象实现了 Symbol.iterator 方法,返回一个迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 自定义可迭代对象
const iterable = {
data: [1, 2, 3],

[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return {
value: this.data[index++],
done: false
};
}
return { done: true };
}
};
}
};

// 可以使用 for...of
for (const value of iterable) {
console.log(value); // 1, 2, 3
}

// 可以使用扩展运算符
console.log([...iterable]); // [1, 2, 3]

3.1.3 内置可迭代对象

JavaScript 中许多内置对象都是可迭代的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 数组
for (const item of [1, 2, 3]) {
console.log(item);
}

// 字符串
for (const char of 'abc') {
console.log(char); // 'a', 'b', 'c'
}

// Map
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value);
}

// Set
const set = new Set([1, 2, 3]);
for (const value of set) {
console.log(value);
}

3.2 生成器(Generator)

3.2.1 生成器函数

生成器函数使用 function* 语法定义,使用 yield 产出值。

1
2
3
4
5
6
7
8
9
10
11
function* counter(limit = 3) {
for (let i = 0; i < limit; i++) {
yield i; // 产出值,暂停执行
}
}

const gen = counter(3);
console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: undefined, done: true }

生成器是迭代器

1
2
3
4
5
6
7
function* gen() {
yield 1;
yield 2;
}

const g = gen();
console.log(g[Symbol.iterator]() === g); // true

3.2.2 yield 关键字

yield 用于产出值并暂停函数执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* generator() {
console.log('开始');
yield 1;
console.log('中间');
yield 2;
console.log('结束');
}

const gen = generator();
console.log('调用 next()');
gen.next(); // 输出:开始
console.log('再次调用 next()');
gen.next(); // 输出:中间
gen.next(); // 输出:结束

yield 的返回值

1
2
3
4
5
6
7
8
9
10
11
function* generator() {
const value1 = yield 1;
console.log('value1:', value1); // '传递给 next 的值'
const value2 = yield 2;
console.log('value2:', value2); // '另一个值'
}

const gen = generator();
gen.next(); // { value: 1, done: false }
gen.next('传递给 next 的值'); // { value: 2, done: false }
gen.next('另一个值'); // { value: undefined, done: true }

3.2.3 yield* 委托

yield* 用于委托给另一个生成器或可迭代对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
function* gen1() {
yield 1;
yield 2;
}

function* gen2() {
yield* gen1(); // 委托给 gen1
yield 3;
}

for (const value of gen2()) {
console.log(value); // 1, 2, 3
}

委托给可迭代对象

1
2
3
4
5
6
7
8
function* gen() {
yield* [1, 2, 3]; // 委托给数组
yield* 'abc'; // 委托给字符串
}

for (const value of gen()) {
console.log(value); // 1, 2, 3, 'a', 'b', 'c'
}

3.2.4 生成器的方法

**return()**:

1
2
3
4
5
6
7
8
9
10
function* gen() {
yield 1;
yield 2;
yield 3;
}

const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return(42)); // { value: 42, done: true }
console.log(g.next()); // { value: undefined, done: true }

**throw()**:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* gen() {
try {
yield 1;
yield 2;
} catch (error) {
console.log('捕获错误:', error);
yield 3;
}
}

const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.throw('错误')); // 捕获错误: 错误
// { value: 3, done: false }

3.3 迭代器/生成器的应用

3.3.1 自定义可迭代数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}

*[Symbol.iterator]() {
for (let i = this.start; i < this.end; i += this.step) {
yield i;
}
}
}

const range = new Range(0, 5);
for (const num of range) {
console.log(num); // 0, 1, 2, 3, 4
}

console.log([...range]); // [0, 1, 2, 3, 4]

3.3.2 惰性序列

生成器可以创建惰性序列,按需生成值,节省内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3

3.3.3 与 for…of、解构、展开配合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* gen() {
yield 1;
yield 2;
yield 3;
}

// for...of
for (const value of gen()) {
console.log(value);
}

// 解构
const [a, b, c] = gen();
console.log(a, b, c); // 1, 2, 3

// 展开
console.log([...gen()]); // [1, 2, 3]

3.3.4 异步生成器

异步生成器用于处理流式异步数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function* fetchPages(url) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();

if (data.length === 0) {
break;
}

yield data;
page++;
}
}

// 使用
async function processPages() {
for await (const page of fetchPages('/api/data')) {
console.log('处理页面:', page);
}
}

4. 实战应用场景

4.1 模块化的实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
// utils/math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// utils/string.js
export const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);

// utils/index.js(统一导出)
export * from './math.js';
export * from './string.js';

// app.js
import { add, capitalize } from './utils/index.js';

4.2 类的实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 事件发射器
class EventEmitter {
constructor() {
this.events = {};
}

on(event, handler) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(handler);
}

emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(handler => handler(...args));
}
}
}

// 使用
const emitter = new EventEmitter();
emitter.on('click', (data) => {
console.log('点击事件:', data);
});
emitter.emit('click', { x: 10, y: 20 });

4.3 生成器的实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 分页数据生成器
async function* paginate(apiUrl) {
let page = 1;
while (true) {
const response = await fetch(`${apiUrl}?page=${page}`);
const data = await response.json();

if (data.length === 0) break;

yield* data; // 产出每一页的数据
page++;
}
}

// 使用
for await (const item of paginate('/api/items')) {
console.log('处理项目:', item);
}

5. 代码片段速查

5.1 模块导出/导入

1
2
3
4
5
6
7
8
9
10
11
12
// 命名导出
export const value = 42;
export function fn() {}

// 默认导出
export default class MyClass {}

// 导入
import MyClass, { value, fn } from './module.js';

// 动态导入
const module = await import('./module.js');

5.2 类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class MyClass {
// 实例字段
name = 'default';

// 静态字段
static version = '1.0';

// 构造函数
constructor(name) {
this.name = name;
}

// 实例方法
getName() {
return this.name;
}

// 静态方法
static create() {
return new MyClass('default');
}

// Getter
get displayName() {
return `Name: ${this.name}`;
}

// Setter
set displayName(value) {
this.name = value;
}
}

5.3 生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 基本生成器
function* gen() {
yield 1;
yield 2;
}

// 无限生成器
function* infinite() {
let i = 0;
while (true) {
yield i++;
}
}

// 异步生成器
async function* asyncGen() {
yield await fetch('/api/data');
}

6. 常见问题与解决方案

6.1 模块循环依赖

问题:模块 A 导入模块 B,模块 B 导入模块 A。

解决方案

  1. 重构代码,提取公共模块
  2. 使用动态导入
  3. 延迟导入

6.2 class 中的 this 绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyClass {
name = 'MyClass';

method() {
console.log(this.name);
}

// ❌ 问题:this 丢失
onClick = () => {
setTimeout(this.method, 1000); // this 是 undefined
}

// ✅ 解决:使用箭头函数
onClick2 = () => {
setTimeout(() => this.method(), 1000); // this 正确
}
}

6.3 生成器的状态管理

1
2
3
4
5
6
7
8
9
10
// 创建新的生成器实例
function* gen() {
yield 1;
}

const g1 = gen();
const g2 = gen(); // 新的实例,独立状态

console.log(g1.next().value); // 1
console.log(g2.next().value); // 1(独立状态)

7. 速记与总结

7.1 核心概念速记

  • “CJS 同步 require 缓存结果;ESM 静态 import 活绑定。”

    • CommonJS 同步加载并缓存;ES6 模块静态分析,使用 live binding。
  • “模块默认严格模式,默认导出唯一,命名导出可多项。”

    • ES6 模块自动严格模式;每个模块只能有一个默认导出,可以有多个命名导出。
  • “class 仍是构造函数+原型;静态在构造上,实例在原型上。”

    • class 是语法糖;静态方法在构造函数上,实例方法在原型上。
  • “extends 先 super 再 this;覆盖时可用 super.method 调父类。”

    • 派生类必须先调用 super();可以使用 super 调用父类方法。
  • “生成器 function + yield;迭代器协议是 next() => {value, done}。”*

    • 生成器使用 function* 和 yield;迭代器实现 next() 方法。

7.2 选择指南

场景 推荐方案 原因
Node.js 项目 CommonJS 或 ESM 根据 Node 版本选择
浏览器项目 ESM 现代浏览器支持
需要动态加载 import() 返回 Promise
需要保持插入顺序 Map 有序
需要去重 Set 自动去重
需要惰性序列 Generator 按需生成

7.3 最佳实践

  1. 优先使用 ES6 模块:更现代,支持静态分析
  2. 避免循环依赖:重构代码结构
  3. 组合优先于继承:更灵活,更易维护
  4. 使用生成器处理大数据:节省内存
  5. 合理使用静态方法:工具函数适合静态方法

结语:模块化和类是 JavaScript 组织代码的重要方式。理解它们的特性和使用场景,能够帮助你编写更清晰、更易维护的代码。生成器和迭代器提供了强大的数据处理能力,特别适合处理流式数据和惰性序列。