我正在尝试在JavaScript中使用继承,并且我发现在子类继承的 Parent 类中具有数组值的问题。下面的代码是正常继承:
var Parent = function() {
this.list = [];
};
var Child = function() {};
Child.prototype = new Parent;
Child.prototype.constructor = Child;
var obj1 = new Child;
obj1.list.push("hello");
console.log(obj1.list); // prints ["hello"];
当我将 Child 对象(继承包含名为 list 的数组变量的Parent)初始化为 obj1 并尝试推送 obj1时.list ,值为“hello”, obj1.list 打印[“你好”] ..到目前为止一直很好。
当我做上面的例子并且我尝试将新的 Child 对象初始化为 obj2 然后推送 obj2 的 list ,值为“goodbye”, obj2.list 现在打印[“hello”,“goodbye”]。 (见下面的代码:)
var obj2 = new Child;
obj2.list.push("goodbye");
console.log(obj2.list); // prints ["hello", "goodbye"];
我可能会有误解,但父中的列表数组会以某种方式保留该值,我不知道为什么。
这是一个很大的麻烦,因为如果我将 Parent 类重用于上述案例中的许多其他子类,如果 Parent 将数组变量共享给其子类,该值也将与其他子类共享,这对我来说是意想不到的。
我期望的是, Child 类代表新对象,当 Child 类初始化为时, Parent 类也是如此obj1 ,然后当我将子对象初始化为 obj2 时, obj1 的推送值不应与 OBJ2
- 问题 -
任何人都可以帮我找出为什么上面示例中的列表(父的数组变量)保留值/共享由Child发起的值对象(在上面的例子中, obj1 和 obj2 )?
如果您有其他解决方案可以解决这个问题,那将是非常好的,但我很乐意首先找出问题。
答案 0 :(得分:12)
当子对象具有从其原型对象继承的属性时,实际发生的是该子对原型的引用,其中包含该属性。孩子没有自己的副本。因此,两个孩子都使用相同的数组 - 您已分配给Parent
的(一个)Child.prototype
原型对象上的数组。
首先是一些图片,然后是更多文字。 : - )
new Parent()
为您提供:
+-----------------+ | Parent instance | +-----------------+ | list = [] | +-----------------+
...然后您将其分配给Child.prototype
。
然后,new Child()
给你这个:
+------------------+ | Child instance 1 | +------------------+ +-----------------+ | (prototype) |------->| Parent instance | +------------------+ +-----------------+ | list = [] | +-----------------+
再次执行new Child()
会为您提供另一个:
+------------------+ +------------------+ | Child instance 1 | | Child instance 2 | +------------------+ +------------------+ | (prototype) |---+ | (prototype) |---+ +------------------+ | +------------------+ | | | | | +-----------------+ +-------------------------+---->| Parent instance | +-----------------+ | list = [] | +-----------------+
正如您所看到的,所有Child
实例都共享相同的Parent
实例(他们的原型),其上有一个数组。
当你说:
var obj = new Child();
obj.list.push("foo");
...这是JavaScript引擎在看到obj.list
时所做的事情:
obj
是否具有名为list
的拥有的属性。如果没有,那么:obj
的原型是否具有名为list
的拥有的属性。如果没有,那么:obj
原型的原型是否具有名为list
的自己的属性。在您的情况下,由于obj
没有拥有 list
属性,因此引擎会查看其原型(您分配给的Parent
实例Child.prototype
),在这种情况下确实拥有该属性。所以使用了一个。它没有复制到孩子或任何东西,它是使用。当然,因为它是一个数组,所以在数组上推送一些东西实际上会把它推到数组上。
如果您要将分配给obj.list
,那么obj
将获得其拥有的 list
属性,从而打破链条。因此,将this.list = [];
放在Child
中会为每个孩子提供自己的列表。
每当原型上有对象引用时,您就会看到这一点,其中对象是可以修改的类型(“可变”对象)。数组,日期,普通对象({}
),RegExps等,它们都有状态,它们都可以被修改,所以你可以用它们看到它。 (String
实例是不可变的,所以尽管发生同样的事情,但是你看不到任何影响,因为字符串不能改变。)
对于原语,虽然你继承它们,但是你不能改变它们的状态(你只能用一个具有不同状态的新原语替换它们),所以你看不到同样的效果。因此,如果obj
从其原型继承属性foo
,而foo
是42
,则alert(obj.foo)
将从原型中获取值并显示“42”。更改foo
的唯一方法是说obj.foo = 67
或类似 - 这会为obj
提供foo
的自己的副本,与原型的副本不同。 (即使您使用++
和--
之类的内容也是如此,例如++obj.foo
;这实际上会被评估为obj.foo = obj.foo + 1
)。
答案 1 :(得分:3)
你在这里缺少的是,Parent.list
只被实例化一次;当你将'原型复制到Child.prototype
时。
在JavaScript中实例化“子类” not 会自动调用父构造函数。因此,Child
的所有实例将共享相同的数组实例。
修改Child
的构造函数以调用父构造函数应该是您寻求治疗疾病的方法:
var Child = function() {
Parent.call(this);
};
显然你已经对这个主题有了一些了解,但是如果你感兴趣的话,这里有一些关于这个主题的文章值得一读: