Javascript继承:Parent的数组变量保留值

时间:2012-02-16 12:10:07

标签: javascript oop inheritance

我正在尝试在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 )?

如果您有其他解决方案可以解决这个问题,那将是非常好的,但我很乐意首先找出问题。

2 个答案:

答案 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时所做的事情:

  1. 查看obj是否具有名为list拥有的属性。如果没有,那么:
  2. 查看obj的原型是否具有名为list拥有的属性。如果没有,那么:
  3. 查看obj原型的原型是否具有名为list自己的属性。
  4. 等。直到我们用完原型。
  5. 在您的情况下,由于obj没有拥有 list属性,因此引擎会查看其原型(您分配给的Parent实例Child.prototype),在这种情况下确实拥有该属性。所以使用了一个。它没有复制到孩子或任何东西,它是使用。当然,因为它是一个数组,所以在数组上推送一些东西实际上会把它推到数组上。

    如果您要分配给obj.list,那么obj将获得其拥有的 list属性,从而打破链条。因此,将this.list = [];放在Child中会为每个孩子提供自己的列表。

    每当原型上有对象引用时,您就会看到这一点,其中对象是可以修改的类型(“可变”对象)。数组,日期,普通对象({}),RegExps等,它们都有状态,它们都可以被修改,所以你可以用它们看到它。 (String实例是不可变的,所以尽管发生同样的事情,但是你看不到任何影响,因为字符串不能改变。)

    对于原语,虽然你继承它们,但是你不能改变它们的状态(你只能用一个具有不同状态的新原语替换它们),所以你看不到同样的效果。因此,如果obj从其原型继承属性foo,而foo42,则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);
};

显然你已经对这个主题有了一些了解,但是如果你感兴趣的话,这里有一些关于这个主题的文章值得一读: