如何为第三方模块的DisplayObject子类实例添加独立的updatables集合?
我尝试扩展第三方模块pixi.js中的DisplayObject类,通过TypeScript接口合并为其添加updatables(Set<Updatable>类型)属性与update方法,并通过原型注入实现相关逻辑:
declare module 'pixi.js' { interface DisplayObject { updatables: Set<Updatable> update(delta: number): void } } DisplayObject.prototype.updatables = new Set() DisplayObject.prototype.update = function (delta: number) { this.updatables.forEach(u => u.update(delta)) // ... }
该实现成功为所有DisplayObject及其子类添加了update方法,但updatables集合为所有实例共享,无法实现每个实例拥有独立的Set。目前我通过懒加载get/set实现了需求,但感觉该方案略显繁琐且不够优雅,想寻求更合适的实现方式。
补充说明:众多第三方模块类继承自DisplayObject,TypeScript接口合并已正确更新所有子类的接口,update方法也已通过原型成功注入,但updatables集合为实例共享而非每个实例独立。
解决方案1:使用Symbol作为属性键+懒初始化
利用Symbol的唯一性,在原型的update方法中为每个实例初始化独立的updatables集合,避免原型共享问题:
// 定义唯一Symbol键,避免属性名冲突 const UPDATABLES_KEY = Symbol('updatables'); declare module 'pixi.js' { interface DisplayObject { [UPDATABLES_KEY]: Set<Updatable> update(delta: number): void } } // 原型注入update方法 DisplayObject.prototype.update = function (delta: number) { // 懒初始化当前实例的updatables集合 if (!this[UPDATABLES_KEY]) { this[UPDATABLES_KEY] = new Set(); } this[UPDATABLES_KEY].forEach(u => u.update(delta)); // ...其他逻辑 }; // 可选:添加getter保持原有的this.updatables访问方式 Object.defineProperty(DisplayObject.prototype, 'updatables', { get() { if (!this[UPDATABLES_KEY]) { this[UPDATABLES_KEY] = new Set(); } return this[UPDATABLES_KEY]; }, enumerable: true, configurable: true });
这种方式既保证了每个实例的updatables独立,又通过Symbol避免了属性名冲突,同时可选的getter可以兼容原有的访问习惯。
解决方案2:用WeakMap存储实例与updatables的映射
使用WeakMap来存储每个实例对应的updatables集合,WeakMap的键是弱引用,不会影响实例的垃圾回收,避免内存泄漏:
const instanceUpdatables = new WeakMap<DisplayObject, Set<Updatable>>(); declare module 'pixi.js' { interface DisplayObject { updatables: Set<Updatable> update(delta: number): void } } DisplayObject.prototype.update = function (delta: number) { let updatables = instanceUpdatables.get(this); if (!updatables) { updatables = new Set(); instanceUpdatables.set(this, updatables); } updatables.forEach(u => u.update(delta)); // ...其他逻辑 }; // 添加getter/setter支持直接访问updatables Object.defineProperty(DisplayObject.prototype, 'updatables', { get() { let updatables = instanceUpdatables.get(this); if (!updatables) { updatables = new Set(); instanceUpdatables.set(this, updatables); } return updatables; }, set(newSet: Set<Updatable>) { instanceUpdatables.set(this, newSet); }, enumerable: true, configurable: true });
这种方式完全不污染实例的属性空间,所有状态都存放在外部的WeakMap中,适合担心属性冲突的场景。
解决方案3:包装DisplayObject构造函数
通过包装原构造函数,在实例创建时为其添加独立的updatables属性:
const OriginalDisplayObject = DisplayObject; // 重写构造函数 DisplayObject = function(...args: any[]) { const instance = new OriginalDisplayObject(...args); // 为当前实例添加独立的updatables instance.updatables = new Set<Updatable>(); return instance; } as typeof OriginalDisplayObject; // 继承原原型链 DisplayObject.prototype = OriginalDisplayObject.prototype; DisplayObject.prototype.constructor = DisplayObject; // 注入update方法 DisplayObject.prototype.update = function (delta: number) { this.updatables.forEach(u => u.update(delta)); // ...其他逻辑 }; // TypeScript接口合并保持不变 declare module 'pixi.js' { interface DisplayObject { updatables: Set<Updatable> update(delta: number): void } }
注意:如果有子类直接调用super(),这种方式依然能正常工作,子类实例也会自动获得独立的updatables集合。
内容的提问来源于stack exchange,提问作者UXK

