模块合成不替代原型链继承,而是通过显式装配独立功能模块来规避其共享状态、耦合构造函数和难以多维混入等缺陷,实现状态隔离、运行时可选、测试友好与渐进升级。

模块合成(Module Composition)不是用来“替代”原型链继承的工具,而是提供一种更可控、更可维护的代码组织思路——尤其在节点系统、可视化编程或插件化架构中。它不否定原型链的价值,而是避开其固有缺陷:共享状态污染、构造函数耦合、难以调试的隐式行为。
为什么原型链继承在复杂场景下容易出问题
当多个子类通过 SubType.prototype = new SuperType() 继承时,父类实例中的引用类型属性(如数组、对象)会被所有子类实例共享。例如:
- 一个基础节点类定义了
this.properties = {},若直接用原型链继承,所有子节点会共用同一个properties对象; - 重写
onExecute时若忘记调用SuperType.prototype.onExecute.call(this),逻辑就断层了; - 无法安全地组合多个“能力”,比如同时需要“支持范围限制”+“支持单位转换”+“支持历史缓存”,原型链天然不支持多维度混入。
模块合成的核心做法:把功能拆成独立可插拔的模块
不靠 extends 或 Object.create 建立父子关系,而是让节点实例在初始化时,显式装配所需的行为模块:
- 定义纯函数或配置驱动的模块,如
withRangeLimit({ min: 0, max: 100 }),返回一个增强后的属性/方法集; - 节点构造函数内部调用这些模块,将结果合并到
this或私有字段上,例如:this.rangeHandler = withRangeLimit({ min, max }); - 执行逻辑中委托调用:
this.setOutputData(0, this.rangeHandler.clamp(this.properties.value));
LiteGraph.js 中的实际迁移示例
对比原先的原型链继承写法:
// ❌ 旧方式:RangedConstant 继承 ConstantNumber,易共享 state
改用模块合成后:
- 保留
ConstantNumber作为最小可用单元,不修改其原型; - 新增
createRangedNode工厂函数,接收基础节点配置,并注入 range 行为; - range 模块本身无副作用,只做数值校验,可被任意节点复用;
- 注册时使用新名字:
LiteGraph.registerNodeType("basic/ranged-const", createRangedNode());
模块合成带来的关键收益
- 状态隔离:每个节点实例拥有自己独立的模块实例,不会相互干扰;
- 运行时可选:同一基础节点,可按需启用/禁用某项能力(如临时关闭范围限制用于调试);
- 测试友好:模块可单独单元测试,无需构造完整节点实例;
- 渐进升级:老节点不动,新功能以模块形式叠加,避免大改重构风险。










