class原型方法动态挂载本质是向prototype添加属性或方法,所有实例共享;但挂载位置、时机和内容类型影响性能、可维护性与隐藏类稳定性,正确方式是直接赋值到myclass.prototype,避免constructor内定义或修改可变对象。

Class 的原型方法动态挂载,本质是往类的 prototype 对象上添加属性或方法,所有实例共享这份逻辑。它不直接增加内存,但挂载方式、时机和内容类型,会显著影响性能、可维护性与隐藏类稳定性。
挂载位置决定是否真正复用
只有挂到 MyClass.prototype 上的方法才被所有实例共用;在 constructor 里用 this.method = function(){} 定义的,是实例独有副本,看似“挂载”,实为内存浪费。
- ✅ 正确共享:
MyClass.prototype.render = function() { ... };或Object.defineProperty(MyClass.prototype, 'version', { value: '1.0' }); - ❌ 伪共享:
class A { constructor() { this.data = {}; } }—— 每个实例都新建一个空对象,与原型无关 - ⚠️ 隐患共享:
MyClass.prototype.config = { timeout: 5000 };—— 若后续被修改(如inst.config.timeout = 8000),V8 可能触发写时复制,破坏共享性
运行时增删带来的隐藏类风险
V8 引擎依赖隐藏类(Hidden Class)做属性查找优化。动态修改 prototype 会打破已有隐藏类结构,导致内联缓存(IC)失效,甚至生成冗余元数据。
- 删除方法:
delete MyClass.prototype.oldMethod→ 已创建的实例可能进入新隐藏类 - 频繁赋值:
for (let i = 0; i {}→ 大量中间隐藏类堆积,短期内存驻留升高 - 建议:原型结构宜在类定义后一次性定型;若需扩展,优先用委托模式或 Symbol 标识能力开关
委托式挂载:轻量替代方案
当功能需按需启用(如不同序列化格式、校验策略),直接往 prototype 塞一堆方法既污染原型又易引发隐藏类分裂。委托模式更可控。
- 定义统一能力池:
const Encoders = { json: obj => JSON.stringify(obj), xml: obj => toXml(obj) }; - 实例只存标识:
this.encoderType = 'json'; - 使用时查表:
Encoders[this.encoderType](this)—— 避免函数重复挂载,也规避原型污染
Class 中 static 与 prototype 的边界必须分清
static 成员属于类本身,不进原型链;只有 class 外层定义的非 static 方法,才会自动挂到 prototype 上。
- ✅
class A { say() {} }→A.prototype.say存在 - ❌
class A { static util() {} }→A.util存在,A.prototype.util不存在 - 常见误用:把纯工具函数写成实例方法(如
formatDate()),结果每个实例都间接持有该函数引用,实则应抽为 static 或独立模块
不复杂但容易忽略:挂载动作本身无害,关键在于是否理解 prototype 是共享契约,而非临时补丁区。











