第9章:类(Classes)
一、导读
1.1 本章学习目标
第9章介绍ES6的class语法。本章核心目标:
- 掌握class语法:构造函数、实例方法
- 理解继承:extends和super
- 掌握静态方法和属性
- 学会使用私有字段:#前缀
- 了解getter和setter
1.2 为什么本章如此重要
class是ES6引入的面向对象编程语法,虽然只是原型继承的语法糖,但让代码更加清晰和易于理解。现代JavaScript框架和库大量使用class。
1.3 核心问题引导
class和原型继承有什么本质关系?
super关键字在构造函数和普通方法中有什么不同?
私有字段为什么需要#前缀?
类的静态方法和实例方法有什么区别?
二、核心概念详解
9.1 类的基础
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
static create(name, age) {
return new Person(name, age);
}
toString() {
return `${this.name} (${this.age})`;
}
}
let p = new Person("Alice", 20);
p.greet();
console.log(Person.create("Bob", 25));9.2 继承
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须先调用super
this.breed = breed;
}
speak() {
super.speak(); // 调用父类方法
console.log(`${this.name} barks`);
}
}
class GuideDog extends Dog {
constructor(name, breed, owner) {
super(name, breed);
this.owner = owner;
}
guide() {
console.log(`${this.name} is guiding ${this.owner}`);
}
}
let dog = new GuideDog("Buddy", "Labrador", "Alice");
dog.speak();
dog.guide();9.3 私有字段和方法
javascript
class BankAccount {
#balance = 0; // 私有字段
#owner;
constructor(owner, initialBalance = 0) {
this.#owner = owner;
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#logTransaction('deposit', amount);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
this.#logTransaction('withdraw', amount);
return true;
}
return false;
}
get balance() {
return this.#balance;
}
#logTransaction(type, amount) {
console.log(`${type}: ${amount}, balance: ${this.#balance}`);
}
}
let account = new BankAccount("Alice", 1000);
account.deposit(500);
console.log(account.balance); // 1500
// console.log(account.#balance); // SyntaxError9.4 类的其他特性
javascript
// getter和setter
class Circle {
#radius;
constructor(radius) {
this.#radius = radius;
}
get radius() {
return this.#radius;
}
set radius(value) {
if (value < 0) throw new Error("Radius cannot be negative");
this.#radius = value;
}
get area() {
return Math.PI * this.#radius ** 2;
}
}
let circle = new Circle(5);
console.log(circle.area); // 78.54...
// 类的表达式形式
const MyClass = class {
constructor() {}
};
// 计算属性名
const methodName = "greet";
class Greeter {
[methodName]() {
console.log("Hello!");
}
}
// 字段初始化
class Config {
theme = "light";
language = "en";
#privateField = "secret";
static defaultTheme = "light";
static #privateStatic = "private";
}三、实战案例
3.1 事件系统
javascript
class EventEmitter {
#listeners = new Map();
on(event, callback) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, []);
}
this.#listeners.get(event).push(callback);
return this; // 链式调用
}
off(event, callback) {
const listeners = this.#listeners.get(event);
if (listeners) {
this.#listeners.set(event,
listeners.filter(cb => cb !== callback));
}
return this;
}
emit(event, ...args) {
const listeners = this.#listeners.get(event);
if (listeners) {
listeners.forEach(cb => cb(...args));
}
return this;
}
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
return this.on(event, wrapper);
}
}
// 使用
const emitter = new EventEmitter();
emitter.on('data', (msg) => console.log(`Received: ${msg}`));
emitter.emit('data', 'Hello');四、本章小结
第9章介绍了ES6的class语法:构造函数、继承(extends/super)、私有字段(#)、getter/setter、静态方法。class是原型继承的语法糖,但更清晰。
五、思考与练习
思考题
class和function constructor有什么本质区别?
为什么子类构造函数必须先调用super?
私有字段和Symbol模拟的私有属性有什么区别?
如何实现Mixin模式?
编程练习
实现一个简单的MVC框架(使用class)。
实现一个带中间件的请求处理器。
使用class实现一个观察者模式。