源本科技 | 码上会

JavaScript 中的多态

2026/01/13
11
0

学习目标

  • 理解多态在面向对象编程中的核心作用

  • 掌握 JavaScript 中实现多态的两种主要方式:方法重写与模拟方法重载

  • 能够在实际项目中应用多态提升代码灵活性和可扩展性

  • 了解多态与其他 OOP 特性(如继承、抽象)的协同关系


什么是多态

多态源自希腊语,意为“多种形态”。在面向对象编程中,它指的是:

同一个接口可以被不同类型的对象以各自的方式实现。

多态的核心价值在于:

  • 解耦调用者与具体实现

  • 支持“开闭原则”(对扩展开放,对修改关闭)

  • 提升代码的可维护性和可扩展性

在 JavaScript 中,由于其动态类型和原型继承特性,多态天然存在且易于实现。


方法重写

运行时多态

这是 JavaScript 中最常见、最自然的多态形式。子类通过重写父类方法,提供特定行为。

基础示例

class Animal {
    speak() {
        console.log("Animal makes a sound");
    }
}

class Dog extends Animal {
    speak() {
        console.log("Dog barks");
    }
}

class Cat extends Animal {
    speak() {
        console.log("Cat meows");
    }
}

// 多态调用
const animals = [new Dog(), new Cat(), new Animal()];

animals.forEach(animal => {
    animal.speak(); // 根据实际对象类型调用对应方法
});
Dog barks
Cat meows
Animal makes a sound

关键特点

  • 运行时决策:JavaScript 引擎在调用时动态确定使用哪个方法

  • 无需类型检查:调用者只需知道对象有 speak() 方法

  • 天然支持:基于原型链的继承机制自动实现


模拟方法重载

编译时多态

JavaScript 不支持传统意义上的方法重载(即同名函数因参数签名不同而共存),但可通过参数检测模拟类似行为。

示例:灵活的计算器

class Calculator {
    add(a, b) {
        // 根据参数数量决定行为
        if (arguments.length === 1) {
            return a + a; // 单参数:翻倍
        } else if (arguments.length === 2) {
            return a + b; // 双参数:相加
        } else {
            throw new Error("Unsupported number of arguments");
        }
    }

    // 更健壮的实现:支持任意数量参数
    sum(...numbers) {
        if (numbers.length === 0) return 0;
        return numbers.reduce((total, num) => total + num, 0);
    }
}

const calc = new Calculator();
console.log(calc.add(5));        // 10
console.log(calc.add(3, 4));     // 7
console.log(calc.sum(1, 2, 3, 4)); // 10

替代方案

使用默认参数或剩余参数

class MathUtils {
    // 使用默认参数实现“重载”效果
    multiply(a, b = a) {
        return a * b;
    }

    // 使用类型检查实现不同行为
    process(value) {
        if (typeof value === 'number') {
            return value * 2;
        } else if (Array.isArray(value)) {
            return value.map(x => x * 2);
        } else if (typeof value === 'string') {
            return value.toUpperCase();
        }
        throw new TypeError("Unsupported type");
    }
}

注意:这不是真正的编译时多态,而是运行时参数分发,但能达到类似效果。


super 关键字

子类在重写方法时,常需调用父类实现,super 提供了这种能力。

class Shape {
    constructor(color) {
        this.color = color;
    }
    
    draw() {
        console.log(`Drawing a ${this.color} shape`);
    }
}

class Circle extends Shape {
    constructor(color, radius) {
        super(color); // 调用父类构造函数
        this.radius = radius;
    }
    
    draw() {
        super.draw(); // 先执行父类逻辑
        console.log(`Circle with radius: ${this.radius}`);
    }
}

const circle = new Circle("red", 10);
circle.draw();

输出:

Drawing a red shape
Circle with radius: 10

应用场景

UI 组件系统

class Component {
    render() {
        throw new Error("Subclass must implement render()");
    }
}

class Button extends Component {
    render() {
        return "<button>Click me</button>";
    }
}

class Input extends Component {
    render() {
        return '<input type="text" placeholder="Enter text">';
    }
}

// 多态渲染
const components = [new Button(), new Input()];
components.forEach(comp => console.log(comp.render()));

数据库适配器

class Database {
    connect() { /* 通用连接逻辑 */ }
    query(sql) { throw new Error("Must be implemented"); }
}

class MySQLAdapter extends Database {
    query(sql) {
        console.log(`Executing MySQL query: ${sql}`);
        // MySQL-specific implementation
    }
}

class MongoDBAdapter extends Database {
    query(filter) {
        console.log(`Executing MongoDB find: ${JSON.stringify(filter)}`);
        // MongoDB-specific implementation
    }
}

文件处理器

class FileHandler {
    parse(content) {
        throw new Error("Subclass must implement parse()");
    }
}

class JSONHandler extends FileHandler {
    parse(content) {
        return JSON.parse(content);
    }
}

class CSVHandler extends FileHandler {
    parse(content) {
        return content.split('\n').map(line => line.split(','));
    }
}

优势与最佳实践

核心优势

  • 代码复用:通用逻辑写在父类,特异性逻辑由子类实现

  • 扩展性强:新增类型无需修改现有调用代码

  • 测试友好:可轻松使用 Mock 对象替代真实实现

  • 架构清晰:符合“针对接口编程,而非针对实现编程”原则

最佳实践

  1. 定义清晰的基类接口
    父类应明确声明哪些方法需要被重写

  2. 避免过度继承
    优先组合而非继承;若行为差异大,考虑策略模式

  3. 使用 instanceof 谨慎
    多态的目的就是避免类型检查,如需检查说明设计可能有问题

  4. 文档化预期行为
    在父类方法注释中说明子类应如何实现


重点总结

  • JavaScript 的多态主要通过方法重写实现,天然支持运行时多态

  • 方法重载需手动模拟,可通过参数数量、类型或剩余参数实现

  • super 关键字允许子类在重写时复用父类逻辑

  • 多态的核心价值是解耦,使系统更易扩展和维护

  • 在 UI、数据处理、适配器等场景中,多态能显著提升代码质量


思考题

  1. Animal 示例中,如果我们创建一个 Bird 类但忘记实现 speak() 方法,会发生什么?这体现了多态的什么特性?

  2. 为什么说“使用 instanceof 判断类型后再调用不同方法”违背了多态的设计初衷?请举例说明更好的做法。

  3. 在函数式编程范式下,如何用高阶函数或组合实现类似多态的效果?与面向对象的多态相比有何优劣?