JavaScript继承本质是原型链,class仅为语法糖,组合是设计策略;应依“是”关系用继承、“有/用”关系用组合,实践中常分层混合使用。

JavaScript 中的继承机制不是“类继承 vs 组合模式”的二选一,而是原型链是底层事实,class 是语法糖,组合是设计策略。真正需要权衡的,是何时用继承表达“是”关系,何时用组合表达“有”或“用”关系。
先看清 class 的本质:它不改变继承逻辑
ES6 的 class 和 extends 看似是传统类继承,但编译后仍是基于 prototype 和 __proto__ 的原型链。比如:
-
Child extends Parent实际做了两件事:把Child.prototype.__proto__指向Parent.prototype(方法复用),再在Child构造函数里调用super()(初始化实例属性); - 你依然可以手动修改
Parent.prototype,所有子类实例立刻获得新方法——这是原型链的活连接特性,和 Java 的静态类继承完全不同; - 如果父类改了
constructor或加了新字段,已有子类实例不会自动更新,但原型上的方法会生效——说明继承的是行为契约,不是结构快照。
什么时候该用继承(class/extends)
适合表达清晰、稳定、语义上属于同一类型层级的关系:
- UI 组件体系:比如
Button、Input、Modal都继承自抽象基类Component,共享render()、destroy()接口; - 领域模型:如
Employee继承Person,因为“员工是一种人”,具备身份、姓名等共性,且业务逻辑中常被统一处理(如权限校验、序列化); - 需要多态调度的场景:比如日志系统中
FileLogger、ConsoleLogger都继承Logger,上层代码只依赖logger.log(),不关心具体实现。
什么时候该用组合(而非继承)
当对象之间的关系更偏向功能委托、能力拼装、运行时可变时,组合更自然、更可控:
- 避免继承爆炸:比如一个
GameCharacter可能同时需要“会飞”、“会隐身”、“带护盾”,若用继承就得写FlyingInvisibleShieldedCharacter这类组合类,而用组合只需挂载FlyBehavior、InvisibilityModule等对象; - 规避原型污染风险:继承链越长,
instanceof判断越模糊,hasOwnProperty和原型查找越难调试;组合让每个模块职责单一,状态隔离明确; - 提升测试与替换能力:你可以轻松把
PaymentService的stripeClient替换为mockClient,而不用动整个继承树; - 应对动态行为:比如用户权限不是固定继承自某个角色,而是运行时根据 token 动态装配
EditorRole+AdminScope,这时 new 一个对象并 assign 行为比 extends 更直接。
实践中常混合使用,关键看抽象粒度
现代 JavaScript 架构往往分层使用:
- 顶层用
class定义核心契约(如DataSource、Validator),保证接口一致; - 中间层用组合封装可插拔能力(如
CacheStrategy、RetryPolicy),通过构造函数或 setter 注入; - 底层数据对象(POJO)尽量避免继承,用纯对象 + 工具函数处理,比如
formatUser(user)而非user.format()。
不复杂但容易忽略:class 提供的是类型声明和开发体验,组合提供的是运行时灵活性,两者不是对立面,而是不同抽象层次的协作工具。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











