目标:掌握 JavaScript 模块化(CommonJS 和 ES6 模块)、类的使用、继承机制,以及生成器和迭代器的应用。
目录
- 模块化
- 类与构造
- Generator 函数与迭代器
- 实战应用场景
- 代码片段速查
- 常见问题与解决方案
- 速记与总结
1. 模块化
模块化是将代码组织成独立、可复用的单元的方式。JavaScript 主要有两种模块系统:CommonJS(Node.js)和 ES6 模块(ESM)。
1.1 CommonJS(Node.js)
CommonJS 是 Node.js 的模块系统,使用 require 和 module.exports。
1.1.1 基本语法
导出模块:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
module.exports = { add: (a, b) => a + b, subtract: (a, b) => a - b };
exports.multiply = (a, b) => a * b; exports.divide = (a, b) => a / b;
|
导入模块:
1 2 3 4 5 6 7
|
const math = require('./math.js'); console.log(math.add(1, 2));
const math2 = require('./math');
|
1.1.2 加载特性
1. 同步加载
CommonJS 是同步加载的,模块会在 require 时立即执行。
1 2 3 4 5 6 7 8 9 10 11 12
| console.log('模块被加载了'); module.exports = { value: 42 };
console.log('开始加载模块'); const mod = require('./module'); console.log('模块加载完成');
|
2. 模块缓存
Node.js 会缓存已加载的模块,同一个模块只会执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13
| let count = 0; module.exports = { increment: () => ++count, getCount: () => count };
const counter1 = require('./counter'); const counter2 = require('./counter');
counter1.increment(); console.log(counter2.getCount());
|
3. 按需执行
模块代码在第一次 require 时执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| console.log('lazy 模块执行了'); module.exports = 'lazy';
console.log('app 开始');
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
| let count = 0; module.exports = count;
const count = require('./counter'); count = 10;
|
对象:引用
1 2 3 4 5 6 7 8
| const state = { count: 0 }; module.exports = state;
const state = require('./state'); state.count = 10; console.log(state.count);
|
1.1.4 循环依赖
CommonJS 的循环依赖可能导致未完全初始化的导出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| console.log('a.js 开始'); const b = require('./b'); console.log('a.js 中 b:', b); module.exports = { name: 'a' };
console.log('b.js 开始'); const a = require('./a'); console.log('b.js 中 a:', a); module.exports = { 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
| console.log('a.js 开始'); let b; module.exports = { name: 'a', getB: () => { if (!b) { b = require('./b'); } return b; } };
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 的官方模块系统,使用 import 和 export。
1.2.1 基本语法
导出模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
export const add = (a, b) => a + b; export const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b; const divide = (a, b) => a / b; export { multiply, divide };
export default function calculate(a, b, op) { return op(a, b); }
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
|
import { add, subtract } from './math.js';
import { add as sum, subtract as sub } from './math.js';
import * as math from './math.js'; console.log(math.add(1, 2));
import calculate from './math.js';
import calculate, { add, subtract } from './math.js';
import './init.js';
|
1.2.2 加载特性
1. 静态分析
ES6 模块在编译期就能确定依赖关系,可以进行静态分析和优化。
1 2 3 4 5 6
| import { add } from './math.js';
|
2. 严格模式
ES6 模块默认处于严格模式。
1 2 3
| export const value = 42;
|
3. 顶层 this 是 undefined
1 2 3 4
| console.log(this);
export const value = 42;
|
1.2.3 Live Binding(活绑定)
ES6 模块导出的是绑定,不是值的拷贝。导出变量的变化会被导入方感知。
1 2 3 4 5 6 7 8 9 10 11 12
| export let count = 0; export function increment() { count++; }
import { count, increment } from './counter.js';
console.log(count); increment(); console.log(count);
|
与 CommonJS 的对比:
1 2 3 4 5 6 7 8 9 10
|
let count = 0; module.exports = { count, increment: () => count++ };
const { count, increment } = require('./counter'); console.log(count); increment(); console.log(count);
|
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
| const routes = { '/home': () => import('./pages/Home.js'), '/about': () => import('./pages/About.js') };
async function processImage(image) { const imageProcessor = await import('./imageProcessor.js'); return imageProcessor.process(image); }
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
| import { b } from './b.js'; export const a = 'a'; console.log('a.js 中 b:', b);
import { a } from './a.js'; export const b = 'b'; console.log('b.js 中 a:', a);
|
最佳实践:避免循环依赖
1 2 3 4 5 6 7 8 9 10 11
|
export const shared = {};
import { shared } from './common.js'; export const a = 'a';
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
| console.log(import.meta.url);
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
| const privateVar = 'private'; export const publicVar = 'public';
import { publicVar } from './module.js'; console.log(publicVar);
|
1.3.2 默认导出
特点:
- 每个模块只能有一个默认导出
1 2 3
| export default function() {}
|
- 导入时可以自定义名称
1 2 3 4 5 6 7
| export default function init() {}
import initApp from './module.js'; import initialize from './module.js'; import anyName from './module.js';
|
- 可以与命名导出混合
1 2 3 4 5 6
| export default class User {} export const VERSION = '1.0';
import User, { VERSION } from './module.js';
|
1.3.3 命名导出
特点:
- 可以有多个命名导出
1 2 3 4 5
| export const a = 1; export const b = 2; export function c() {} export class D {}
|
- 导入时必须使用解构
1 2 3 4
| import { a, b, c, D } from './module.js';
import { a as aliasA } from './module.js';
|
- 可以重新导出
1 2 3 4 5
| 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
|
let count = 0; module.exports = { count, increment: () => count++ };
const { count, increment } = require('./counter'); console.log(count); increment(); console.log(count);
export let count = 0; export function increment() { count++; }
import { count, increment } from './counter.js'; console.log(count); increment(); console.log(count);
|
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 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); console.log(user.__proto__ === User.prototype); console.log(typeof User);
|
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); console.log(user.age);
|
默认构造函数:
如果没有定义 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); console.log(user.name);
|
类字段的执行顺序:
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);
|
私有字段(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.getPrivate());
|
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)); console.log(Util.version);
const util = new Util();
|
静态方法的 this:
1 2 3 4 5 6 7
| class MyClass { static method() { console.log(this === MyClass); } }
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()); console.log(user.getName());
console.log(user.say === User.prototype.say);
|
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); console.log(rect.perimeter);
|
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; console.log(rect.width);
|
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); this.breed = breed; } speak() { return `${this.name} barks`; } }
const dog = new Dog('旺财', '金毛'); console.log(dog.speak()); console.log(dog.name);
|
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); this.breed = breed; } move() { return super.move() + ' on four legs'; } }
|
重要规则:
在派生类的构造函数中,必须先调用 super() 才能使用 this。
1 2 3 4 5 6 7
| class Child extends Parent { constructor() { 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 { }
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()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
|
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 (const value of iterable) { console.log(value); }
console.log([...iterable]);
|
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); }
const map = new Map([['a', 1], ['b', 2]]); for (const [key, value] of map) { console.log(key, value); }
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()); console.log(gen.next()); console.log(gen.next()); console.log(gen.next());
|
生成器是迭代器:
1 2 3 4 5 6 7
| function* gen() { yield 1; yield 2; }
const g = gen(); console.log(g[Symbol.iterator]() === g);
|
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); const value2 = yield 2; console.log('value2:', value2); }
const gen = generator(); gen.next(); gen.next('传递给 next 的值'); gen.next('另一个值');
|
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(); yield 3; }
for (const value of gen2()) { console.log(value); }
|
委托给可迭代对象:
1 2 3 4 5 6 7 8
| function* gen() { yield* [1, 2, 3]; yield* 'abc'; }
for (const value of gen()) { console.log(value); }
|
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()); console.log(g.return(42)); console.log(g.next());
|
**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()); console.log(g.throw('错误'));
|
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); }
console.log([...range]);
|
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); console.log(fib.next().value); console.log(fib.next().value); console.log(fib.next().value); console.log(fib.next().value);
|
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 (const value of gen()) { console.log(value); }
const [a, b, c] = gen(); console.log(a, b, c);
console.log([...gen()]);
|
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
| export const add = (a, b) => a + b; export const subtract = (a, b) => a - b;
export const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
export * from './math.js'; export * from './string.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'); } get displayName() { return `Name: ${this.name}`; } 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。
解决方案:
- 重构代码,提取公共模块
- 使用动态导入
- 延迟导入
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); } onClick = () => { setTimeout(this.method, 1000); } onClick2 = () => { setTimeout(() => this.method(), 1000); } }
|
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); console.log(g2.next().value);
|
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 最佳实践
- 优先使用 ES6 模块:更现代,支持静态分析
- 避免循环依赖:重构代码结构
- 组合优先于继承:更灵活,更易维护
- 使用生成器处理大数据:节省内存
- 合理使用静态方法:工具函数适合静态方法
结语:模块化和类是 JavaScript 组织代码的重要方式。理解它们的特性和使用场景,能够帮助你编写更清晰、更易维护的代码。生成器和迭代器提供了强大的数据处理能力,特别适合处理流式数据和惰性序列。