JavaScript组件系统问题:实例组件引用被覆盖的原因及解决方法
这问题我刚学JS原型继承的时候也踩过一模一样的坑!咱们先拆解下问题出在哪,再给你几个可行的实现方案:
问题核心原因
你用Object.create(Base)创建实例时,忽略了原型上的引用类型属性是所有实例共享的这个关键点:
Base对象里的_Comp是一个引用类型(对象),当你通过Object.create(Base)创建MyBase时,MyBase的原型会指向Base本身。这意味着,如果你没有给MyBase自身添加_Comp属性,所有实例访问的_Comp其实都是原型上的同一个Base._Comp对象!- 所以创建实例A时,你给这个共享的
_Comp添加了Component并绑定了A的引用;但创建实例B时,你又修改了同一个共享_Comp里的Component,把它的_MyBase改成了B。最终A和B访问的都是这个被覆盖后的共享组件,自然两次日志都输出"B"。
解决方案
方案1:给每个实例创建独立的_Comp对象
最简单的修改方式,就是在CreateBase函数里,给每个新创建的MyBase实例添加自己的_Comp属性,不再使用原型上的共享对象:
// Base const Base = { _sName:"", // 这里保留_Comp的定义,但实例会覆盖它 _Comp:{}, }; // Component var Component = { Constructor:function( MyBase ) { this._MyBase = MyBase; } }; // Factory function function CreateBase( sName ) { let MyBase = Object.create( Base ); MyBase._sName = sName; // 关键:给当前实例创建独立的_Comp,覆盖原型上的共享对象 MyBase._Comp = {}; MyBase._Comp.Component = Object.create( Component ); MyBase._Comp.Component.Constructor( MyBase ); return MyBase; }; // 创建测试Base实例 var A = CreateBase( "A" ); var B = CreateBase( "B" ); console.log( A._Comp.Component._MyBase._sName ); // 输出A console.log( B._Comp.Component._MyBase._sName ); // 输出B
方案2:改用构造函数模式(更符合JS类式编程习惯)
如果想更规范地实现组件系统,建议把Base和Component改成构造函数,这样每个实例会自动拥有自己的属性,避免共享问题:
// 构造函数版Base,每个实例都会初始化自己的_sName和_Comp function Base(sName) { this._sName = sName; this._Comp = {}; } // 构造函数版Component,直接在创建时绑定所属Base function Component(myBase) { this._MyBase = myBase; } // 工厂函数简化 function CreateBase(sName) { const myBase = new Base(sName); myBase._Comp.Component = new Component(myBase); return myBase; } // 测试 var A = CreateBase("A"); var B = CreateBase("B"); console.log(A._Comp.Component._MyBase._sName); // A console.log(B._Comp.Component._MyBase._sName); // B
方案3:原型模式下添加初始化方法
如果坚持用对象字面量的原型风格,可以给Base添加一个初始化方法,确保每个实例创建时初始化自己的_Comp:
const Base = { _sName: "", // 初始化方法,给实例添加独立属性 init(sName) { this._sName = sName; this._Comp = {}; return this; } }; const Component = { init(myBase) { this._MyBase = myBase; return this; } }; function CreateBase(sName) { let myBase = Object.create(Base).init(sName); myBase._Comp.Component = Object.create(Component).init(myBase); return myBase; } // 测试 var A = CreateBase("A"); var B = CreateBase("B"); console.log(A._Comp.Component._MyBase._sName); // A console.log(B._Comp.Component._MyBase._sName); // B
总结
本质上就是要避免多个实例共享同一个引用类型的原型属性,只要确保每个Base实例拥有自己独立的_Comp对象,组件就能正确绑定所属的Base实例啦。
内容的提问来源于stack exchange,提问作者JGodo
相关产品推荐
相关产品推荐

