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