JavaScript通过运行时动态分发与统一接口约定实现多态,包括继承式抽象方法、鸭子类型、工厂模式及参数判别模拟重载,核心是接口一致与行为可预期。

JavaScript 的 class 本身不提供编译期多态(如 Java 那样的方法重载),但通过**运行时动态分发 + 统一接口约定**,完全可以实现实用、灵活的多态性,尤其适合编写可复用的通用组件。
统一方法名 + 子类按需实现
这是最直接也最常用的多态写法。父类定义“契约”(即公共方法名),子类各自实现具体逻辑,调用方只认方法名,不关心具体类型。
- 父类声明抽象行为(哪怕只是空方法或 throw 提示)
- 每个子类覆盖该方法,提供符合自身语义的实现
- 组件接收任意满足该接口的对象,运行时自动调用对应版本
例如一个通用表格渲染器:
class TableRow {
render() {
throw new Error('TableRow.render() must be implemented');
}
}
<p>class UserRow extends TableRow {
constructor(user) {
super();
this.user = user;
}
render() {
return <code><tr><td>${this.user.name}</td><td>${this.user.email}</td></tr></code>;
}
}</p><p>class ProductRow extends TableRow {
constructor(product) {
super();
this.product = product;
}
render() {
return <code><tr><td>${this.product.title}</td><td>$${this.product.price}</td></tr></code>;
}
}</p><p>// 通用渲染函数 —— 不知道也不需要知道具体类型
function renderTable(rows) {
return <code><table>${rows.map(row => row.render()).join('')}</table></code>;
}</p><p>renderTable([
new UserRow({ name: 'Alice', email: 'a@b.com' }),
new ProductRow({ title: 'Laptop', price: 1299 })
]); // ✅ 自动调用各自 render()
利用鸭子类型避免强继承依赖
不必非要让所有类都继承同一个基类。只要对象有某个方法,就能被通用逻辑消费——这就是 JavaScript 天然支持的“鸭子类型”,是更轻量、更解耦的多态实践。
- 组件函数只检查目标对象是否具备必要方法(
typeof obj.draw === 'function') - 传入的对象可以来自任意构造方式:class 实例、字面量对象、工厂函数返回值
- 没有继承树负担,适合插件化、配置驱动的组件设计
比如一个图表绘制组件:
function drawChart(container, chartData, renderer) {
if (typeof renderer.render !== 'function') {
throw new Error('renderer must have a render() method');
}
container.innerHTML = renderer.render(chartData);
}
<p>// 任意对象,只要带 render 方法即可
drawChart(
document.getElementById('chart'),
{ labels: ['Jan', 'Feb'], values: [24, 37] },
{
render(data) {
return <code><canvas width="400" height="200"></canvas></code>; // SVG 或 Canvas 渲染逻辑
}
}
);
配合 static 方法与 factory 模式提升灵活性
当组件需要根据参数动态选择行为时,可在 class 上定义 static create() 或使用工厂函数,把“创建哪个子类”的逻辑封装起来,对外保持统一接口。
- 用户仍只和一个名字交互(如
Button.create(type)) - 内部根据 type 返回不同子类实例,各子类实现自己的
render()、handleClick() - 扩展新类型只需新增子类 + 修改 factory,不改动已有调用代码
示例:
class Button {
static create(type, config) {
switch (type) {
case 'primary': return new PrimaryButton(config);
case 'outline': return new OutlineButton(config);
case 'icon': return new IconButton(config);
default: throw new Error(`Unknown button type: ${type}`);
}
}
handleClick() { /* 默认防抖/日志等共用逻辑 */ }
}
<p>class PrimaryButton extends Button {
render() { return <code><button class="btn-primary">${this.text}</button></code>; }
}
// … 其他子类略</p><p>// 使用方完全无感知差异
const btn = Button.create('outline', { text: 'Save' });
document.body.appendChild(document.createElement('div')).innerHTML = btn.render();
注意边界:JS 没有重载,但可用参数判别模拟
严格来说,JS 不支持基于参数签名的重载(同名函数多个定义)。但可通过参数类型/数量做运行时分支,达到类似效果,适合配置型组件。
- 在方法内用
arguments.length、typeof、Array.isArray()等判断 - 适用于构造函数或初始化方法,让同一组件支持多种初始化方式
- 比写多个方法名更简洁,但需文档明确说明行为差异
例如:
class Modal {
constructor(options) {
if (typeof options === 'string') {
// 字符串:当作内容文本
this.content = options;
this.title = 'Info';
} else if (options && typeof options === 'object') {
// 对象:完整配置
this.title = options.title || 'Modal';
this.content = options.content;
this.onClose = options.onClose;
}
}
}
不复杂但容易忽略:多态的价值不在语法炫技,而在于让组件使用者只关注“做什么”,不用操心“怎么做”。只要守住接口一致、行为可预期这两条线,class 语法就能支撑起健壮又易扩展的通用组件体系。


















