用户工具

站点工具


javascript:第九章面向对象编程

第九章 面向对象编程

本章目标

  • 理解面向对象编程(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应用至关重要。

javascript/第九章面向对象编程.txt · 最后更改: 127.0.0.1