====== 第九章 面向对象编程 ======
===== 本章目标 =====
* 理解面向对象编程(OOP)的核心概念
* 掌握JavaScript中的构造函数和原型
* 深入理解ES6类的语法和特性
* 学会使用封装、继承和多态
* 掌握私有字段和静态成员的使用
===== 9.1 面向对象编程基础 =====
==== 9.1.1 什么是面向对象编程 ====
面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它将程序组织为对象的集合,每个对象包含数据(属性)和操作数据的方法。
**OOP的四大特性:**
* **封装**:将数据和操作数据的方法绑定在一起,隐藏内部实现细节
* **继承**:子类可以继承父类的属性和方法,实现代码复用
* **多态**:同一操作作用于不同的对象可以有不同的解释
* **抽象**:提取事物的本质特征,忽略非本质细节
==== 9.1.2 JavaScript中的OOP ====
JavaScript是一门基于原型的面向对象语言,与其他基于类的语言(如Java、C++)有所不同。
// 基于原型的对象创建
const person = {
name: '张三',
age: 25,
greet: function() {
console.log('你好,我是' + this.name);
}
};
person.greet(); // 你好,我是张三
===== 9.2 构造函数与原型 =====
==== 9.2.1 构造函数 ====
构造函数是用于创建和初始化对象的函数。按照约定,构造函数名首字母大写。
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log('你好,我是' + this.name);
};
}
const person1 = new Person('张三', 25);
const person2 = new Person('李四', 30);
person1.greet(); // 你好,我是张三
person2.greet(); // 你好,我是李四
// 每个实例都有自己的greet方法,占用额外内存
console.log(person1.greet === person2.greet); // false
==== 9.2.2 原型(Prototype) ====
每个JavaScript对象都有一个原型对象,对象可以从原型继承属性和方法。使用原型可以节省内存,因为所有实例共享同一个方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
// 将方法定义在原型上
Person.prototype.greet = function() {
console.log('你好,我是' + this.name);
};
Person.prototype.getAge = function() {
return this.age;
};
const person1 = new Person('张三', 25);
const person2 = new Person('李四', 30);
// 所有实例共享原型上的方法
console.log(person1.greet === person2.greet); // true
person1.greet(); // 你好,我是张三
console.log(person2.getAge()); // 30
==== 9.2.3 原型链 ====
当访问对象的属性或方法时,JavaScript会沿着原型链向上查找,直到找到该属性或到达原型链的顶端(null)。
// 原型链示例
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + '发出声音');
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 建立原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
console.log(this.name + '汪汪叫');
};
const dog = new Dog('旺财', '金毛');
dog.speak(); // 旺财汪汪叫
// 检查原型链
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
===== 9.3 ES6类(Class) =====
==== 9.3.1 类的基本语法 ====
ES6引入了class关键字,提供了更简洁、更清晰的面向对象编程语法。class本质上仍是原型的语法糖。
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
console.log(`你好,我是${this.name}`);
}
getInfo() {
return `${this.name},${this.age}岁`;
}
}
const person = new Person('张三', 25);
person.greet(); // 你好,我是张三
console.log(person.getInfo()); // 张三,25岁
==== 9.3.2 类的继承 ====
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}发出声音`);
}
move() {
console.log(`${this.name}在移动`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
// 重写父类方法
speak() {
console.log(`${this.name}汪汪叫`);
}
// 子类特有方法
fetch() {
console.log(`${this.name}去捡球`);
}
}
class Cat extends Animal {
speak() {
console.log(`${this.name}喵喵叫`);
}
climb() {
console.log(`${this.name}在爬树`);
}
}
const dog = new Dog('旺财', '金毛');
const cat = new Cat('咪咪');
dog.speak(); // 旺财汪汪叫
dog.move(); // 旺财在移动
dog.fetch(); // 旺财去捡球
cat.speak(); // 咪咪喵喵叫
cat.climb(); // 咪咪在爬树
==== 9.3.3 super关键字 ====
super关键字用于调用父类的方法或构造函数。
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
getInfo() {
return `矩形:${this.width} x ${this.height}`;
}
}
class Square extends Rectangle {
constructor(side) {
super(side, side); // 调用父类构造函数
this.side = side;
}
getInfo() {
// 调用父类的getInfo方法
return `正方形:边长${this.side},继承自${super.getInfo()}`;
}
}
const square = new Square(5);
console.log(square.getArea()); // 25
console.log(square.getInfo()); // 正方形:边长5,继承自矩形:5 x 5
===== 9.4 封装与访问控制 =====
==== 9.4.1 私有字段(ES2022) ====
ES2022引入了真正的私有字段,使用#前缀表示,只能在类的内部访问。
class BankAccount {
// 私有字段
#balance = 0;
#accountNumber;
constructor(accountNumber, initialBalance) {
this.#accountNumber = accountNumber;
this.#balance = initialBalance;
}
// 公共方法
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`存入${amount}元,当前余额:${this.#balance}元`);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`取出${amount}元,当前余额:${this.#balance}元`);
} else {
console.log('余额不足');
}
}
getBalance() {
return this.#balance;
}
// 私有方法
#validateAmount(amount) {
return amount > 0 && typeof amount === 'number';
}
}
const account = new BankAccount('123456', 1000);
account.deposit(500); // 存入500元,当前余额:1500元
account.withdraw(200); // 取出200元,当前余额:1300元
console.log(account.getBalance()); // 1300
// 以下都会报错
// console.log(account.#balance); // SyntaxError
// account.#validateAmount(100); // SyntaxError
==== 9.4.2 Getter和Setter ====
Getter和Setter用于控制属性的访问和赋值。
class Temperature {
constructor(celsius) {
this._celsius = celsius;
}
// Getter:摄氏度
get celsius() {
return this._celsius;
}
// Setter:摄氏度
set celsius(value) {
if (value < -273.15) {
throw new Error('温度不能低于绝对零度');
}
this._celsius = value;
}
// Getter:华氏度
get fahrenheit() {
return this._celsius * 9 / 5 + 32;
}
// Setter:华氏度
set fahrenheit(value) {
this._celsius = (value - 32) * 5 / 9;
}
// Getter:开尔文
get kelvin() {
return this._celsius + 273.15;
}
}
const temp = new Temperature(25);
console.log(temp.celsius); // 25
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 86;
console.log(temp.celsius); // 30
===== 9.5 静态成员 =====
==== 9.5.1 静态方法和属性 ====
静态方法和属性属于类本身,而不是类的实例。使用static关键字定义。
class MathUtils {
// 静态属性
static PI = 3.14159;
// 静态方法
static add(a, b) {
return a + b;
}
static subtract(a, b) {
return a - b;
}
static multiply(a, b) {
return a * b;
}
static circleArea(radius) {
return this.PI * radius * radius;
}
}
// 直接通过类调用
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.circleArea(5)); // 78.53975
// 不能通过实例调用
const utils = new MathUtils();
// utils.add(1, 2); // TypeError: utils.add is not a function
==== 9.5.2 静态块(ES2022) ====
ES2022引入了静态块,用于执行类的静态初始化逻辑。
class Database {
static connection = null;
static config = {};
// 静态块
static {
console.log('执行静态初始化块');
this.config = {
host: 'localhost',
port: 3306,
database: 'myapp'
};
}
static connect() {
if (!this.connection) {
this.connection = `连接到${this.config.host}:${this.config.port}`;
}
return this.connection;
}
}
console.log(Database.connect()); // 执行静态初始化块 → 连接到localhost:3306
===== 9.6 多态 =====
多态允许子类以不同的方式实现父类的方法。
class Shape {
getArea() {
throw new Error('子类必须实现getArea方法');
}
getPerimeter() {
throw new Error('子类必须实现getPerimeter方法');
}
describe() {
return `面积:${this.getArea()},周长:${this.getPerimeter()}`;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
getPerimeter() {
return 2 * (this.width + this.height);
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
getPerimeter() {
return 2 * Math.PI * this.radius;
}
}
class Triangle extends Shape {
constructor(a, b, c) {
super();
this.a = a;
this.b = b;
this.c = c;
}
getArea() {
const s = (this.a + this.b + this.c) / 2;
return Math.sqrt(s * (s - this.a) * (s - this.b) * (s - this.c));
}
getPerimeter() {
return this.a + this.b + this.c;
}
}
// 使用多态
const shapes = [
new Rectangle(5, 3),
new Circle(5),
new Triangle(3, 4, 5)
];
shapes.forEach(function(shape) {
console.log(shape.describe());
});
===== 9.7 抽象类与接口 =====
==== 9.7.1 模拟抽象类 ====
JavaScript没有内置的抽象类,但可以通过抛出错误来模拟。
class Animal {
constructor(name) {
if (new.target === Animal) {
throw new Error('Animal是抽象类,不能直接实例化');
}
this.name = name;
}
speak() {
throw new Error('子类必须实现speak方法');
}
move() {
console.log(`${this.name}在移动`);
}
}
// const animal = new Animal('test'); // Error: Animal是抽象类,不能直接实例化
class Dog extends Animal {
speak() {
console.log(`${this.name}汪汪叫`);
}
}
const dog = new Dog('旺财');
dog.speak(); // 旺财汪汪叫
==== 9.7.2 Mixin模式 ====
Mixin是一种代码复用模式,用于在不使用多重继承的情况下共享功能。
// 定义Mixin
const Flyable = {
fly() {
console.log(`${this.name}在飞翔`);
},
land() {
console.log(`${this.name}降落了`);
}
};
const Swimmable = {
swim() {
console.log(`${this.name}在游泳`);
},
dive() {
console.log(`${this.name}潜入水中`);
}
};
// 应用Mixin的辅助函数
function mixin(target, ...sources) {
Object.assign(target.prototype, ...sources);
}
class Bird {
constructor(name) {
this.name = name;
}
}
mixin(Bird, Flyable);
class Fish {
constructor(name) {
this.name = name;
}
}
mixin(Fish, Swimmable);
class Duck {
constructor(name) {
this.name = name;
}
}
mixin(Duck, Flyable, Swimmable);
const bird = new Bird('麻雀');
bird.fly(); // 麻雀在飞翔
const duck = new Duck('唐老鸭');
duck.fly(); // 唐老鸭在飞翔
duck.swim(); // 唐老鸭在游泳
===== 9.8 实战案例 =====
==== 9.8.1 设计一个组件系统 ====
class Component {
constructor(props = {}) {
this.props = props;
this.state = {};
this.element = null;
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.render();
}
mount(container) {
this.element = this.render();
container.appendChild(this.element);
}
render() {
throw new Error('子类必须实现render方法');
}
}
class Button extends Component {
constructor(props) {
super(props);
this.state = { clicked: 0 };
}
render() {
const button = document.createElement('button');
button.textContent = `${this.props.label} (点击${this.state.clicked}次)`;
button.onclick = () => {
this.setState({ clicked: this.state.clicked + 1 });
if (this.props.onClick) {
this.props.onClick();
}
};
return button;
}
}
class Input extends Component {
render() {
const input = document.createElement('input');
input.type = this.props.type || 'text';
input.placeholder = this.props.placeholder || '';
input.onchange = (e) => {
if (this.props.onChange) {
this.props.onChange(e.target.value);
}
};
return input;
}
}
// 使用
// const button = new Button({ label: '点击我', onClick: () => console.log('clicked') });
// button.mount(document.body);
===== 本章习题 =====
==== 基础练习 ====
**练习1:类和对象**
创建一个Car类,包含品牌、型号、年份属性,以及启动、停止、获取信息的方法。
**练习2:继承**
创建ElectricCar类继承Car,添加电池容量属性和充电方法。
**练习3:封装**
创建一个Counter类,使用私有字段存储计数值,提供增加、减少、重置和获取值的方法。
==== 进阶练习 ====
**练习4:多态设计**
设计一个图形系统,包含Shape抽象类,以及Circle、Rectangle、Triangle等具体类,实现面积和周长的计算。
**练习5:Mixin实现**
实现一个EventEmitter Mixin,使任何类都具有事件发布订阅能力。
const EventEmitter = {
on(event, callback) { /* 实现 */ },
emit(event, data) { /* 实现 */ },
off(event, callback) { /* 实现 */ }
};
**练习6:设计模式应用**
使用单例模式设计一个配置管理器类,确保整个应用中只有一个配置实例。
==== 思考题 ====
1. JavaScript的class和传统的面向对象语言(如Java)有什么本质区别?
2. 私有字段(#)和下划线前缀(_)的私有约定有什么区别?
3. 什么时候应该使用静态方法?
4. 原型继承和类继承各有什么优缺点?
===== 参考答案 =====
**练习3答案:**
class Counter {
#count = 0;
increment() {
this.#count++;
}
decrement() {
if (this.#count > 0) {
this.#count--;
}
}
reset() {
this.#count = 0;
}
getValue() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
counter.increment();
console.log(counter.getValue()); // 2
**练习6答案:**
class ConfigManager {
static #instance = null;
#config = {};
constructor() {
if (ConfigManager.#instance) {
return ConfigManager.#instance;
}
ConfigManager.#instance = this;
}
static getInstance() {
if (!ConfigManager.#instance) {
ConfigManager.#instance = new ConfigManager();
}
return ConfigManager.#instance;
}
set(key, value) {
this.#config[key] = value;
}
get(key) {
return this.#config[key];
}
}
const config1 = ConfigManager.getInstance();
const config2 = ConfigManager.getInstance();
console.log(config1 === config2); // true
===== 小结 =====
本章我们学习了JavaScript面向对象编程的核心概念:
* **OOP基础**:封装、继承、多态、抽象
* **构造函数与原型**:理解JavaScript的对象创建机制
* **ES6类**:更清晰的面向对象语法
* **访问控制**:私有字段、Getter/Setter
* **静态成员**:类级别的属性和方法
* **设计模式**:抽象类、Mixin、单例模式
掌握面向对象编程对于构建大型、可维护的JavaScript应用至关重要。