JavaScript多态通过原型链、动态绑定和运行时方法查找实现,典型表现为子类重写父类同名方法并自动调用子类版本;支持构造函数+原型链、ES6 class及鸭子类型三种形式。

JavaScript 中的多态性并不像 Java 或 C++ 那样依赖编译时类型系统,而是通过原型链、动态绑定和运行时方法查找自然体现的。它最典型、最实用的场景,就是子对象(或子构造函数实例)对父对象同名方法的重写,并在调用时自动执行子类版本——即“同一接口,不同实现”。
构造函数 + 原型链实现方法重写
这是最经典的模拟继承与多态的方式。父构造函数定义通用方法,子构造函数在其原型上覆盖同名方法,实例调用时会优先使用自身原型上的版本。
例如:
function Animal() {}
Animal.prototype.speak = function() {
console.log("Some sound");
};
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 方法重写:同名方法,不同行为
Dog.prototype.speak = function() {
console.log("Woof!");
};
const dog = new Dog();
dog.speak(); // 输出 "Woof!" —— 调用的是 Dog 原型上的 speak,不是 Animal 的
关键点:
立即学习“Java免费学习笔记(深入)”;
- 子类原型必须正确链接到父类原型(Object.create(Animal.prototype)),否则无法继承且重写无效
- 重写发生在 子类原型对象上,而非实例本身(保持内存高效)
- 调用时 JS 引擎沿原型链查找,找到第一个匹配的方法即执行,天然支持多态
ES6 class 语法下的更清晰表达
class 是语法糖,底层仍是原型机制,但语义更明确,extends 和 super 让重写与复用更直观。
例如:
class Animal {
speak() {
console.log("Some sound");
}
}
class Cat extends Animal {
speak() { // 明确重写
console.log("Meow!");
}
}
class Bird extends Animal {
speak() {
console.log("Chirp!");
}
}
const animals = [new Cat(), new Bird(), new Animal()];
animals.forEach(animal => animal.speak());
// Meow! → Chirp! → Some sound
这种“同一调用(animal.speak()),不同输出”的行为,就是多态性的直接体现。数组中混入不同类型实例,无需判断类型,统一调用即可获得各自定制的行为。
对象字面量与动态方法替换也构成多态
JavaScript 的灵活性还体现在:即使没有显式继承,只要多个对象拥有同名方法,且该方法被统一调用,就构成轻量级多态。
例如:
const button = {
render() { return "<button>Click me</button>"; }
};
const input = {
render() { return "<input type='text'>"; }
};
const renderUI = (component) => component.render(); // 统一接口
console.log(renderUI(button)); // "<button>Click me</button>"
console.log(renderUI(input)); // "<input type='text'>"
这里没有继承关系,但 render 是契约式方法名,不同对象提供各自实现,调用方不关心具体类型——这正是鸭子类型(Duck Typing)支撑的多态,也是 JavaScript 最自然的多态风格。
注意:重写不等于覆盖实例属性,也不应忽略 super 调用
若子类方法需复用父类逻辑,应显式调用 super.methodName();否则可能丢失基础行为。
例如:
class Animal {
constructor(name) {
this.name = name;
}
introduce() {
console.log(`I'm ${this.name}`);
}
}
class Human extends Animal {
introduce() {
super.introduce(); // 复用父逻辑
console.log("and I walk upright.");
}
}
错误做法是直接在实例上赋值重写(如 dog.speak = () => {...}),虽能工作,但破坏了原型复用性,每个实例都持有一份独立函数,失去多态设计本意。








