Javascript";“面向对象”;以及具有多级继承的原型

Javascript";“面向对象”;以及具有多级继承的原型,javascript,inheritance,prototype,Javascript,Inheritance,Prototype,我是Javascript编程新手,我正在从面向对象编程的角度研究我的第一个应用程序(实际上是一个游戏)(我知道js并不是真正面向对象的,但对于这个特定的问题,我更容易这样开始) 我有一个“类”的层次结构,最上面的(“事物”类)定义了一个相关事物的列表(游戏中的附加项目)。它由ThingA类继承,ThingA类由ThingA1和ThingA2类继承 最简单的例子是: function Thing() { this.relatedThings = []; } Thing.prototype

我是Javascript编程新手,我正在从面向对象编程的角度研究我的第一个应用程序(实际上是一个游戏)(我知道js并不是真正面向对象的,但对于这个特定的问题,我更容易这样开始)

我有一个“类”的层次结构,最上面的(“事物”类)定义了一个相关事物的列表(游戏中的附加项目)。它由ThingA类继承,ThingA类由ThingA1和ThingA2类继承

最简单的例子是:

function Thing() 
{
  this.relatedThings   = [];
}
Thing.prototype.relateThing = function(what)
{
  this.relatedThings.push(what);
}

ThingA.prototype = new Thing();
ThingA.prototype.constructor = ThingA;
function ThingA()
{
}

ThingA1.prototype = new ThingA();
ThingA1.prototype.constructor = ThingA1;
function ThingA1()
{

}

ThingA2.prototype = new ThingA();
ThingA2.prototype.constructor = ThingA2;
function ThingA2()
{    
}

var thingList = [];

thingList.push(new ThingA());
thingList.push(new ThingA1());
thingList.push(new ThingA2());
thingList.push(new ThingA2());
thingList.push(new Thing());

thingList[1].relateThing('hello');
在代码末尾,当执行relateting时,每个ThingA、ThingA1和ThingA2都将执行它(而不是数组中的最后一个“Thing”对象)。我发现如果我在ThingA原型中定义Relateting函数,它将正常工作。因为游戏是如何设计的,我宁愿不这样做

也许我不了解javascript中原型的工作原理。我知道该函数在所有对象之间共享,但我猜执行将是单独的。有人能解释一下为什么会发生这种情况以及如何解决它吗?我不知道我是否做了错误的继承,或者原型定义,或者什么


提前感谢。

欢迎使用原型链

让我们看看在您的示例中它是什么样子

问题 调用
newthing()
时,您正在创建一个新对象,该对象的属性
relatedThings
引用了一个数组。所以我们可以说我们有:

+--------------+
|Thing instance|
|              |
| relatedThings|----> Array
+--------------+     
然后将此实例分配给
ThingA.prototype

+--------------+
|    ThingA    |      +--------------+
|              |      |Thing instance|
|   prototype  |----> |              |
+--------------+      | relatedThings|----> Array
                      +--------------+
因此
ThingA
的每个实例都将从
Thing
实例继承。现在,您将创建
ThingA1
ThingA2
并为每个原型分配一个新的
ThingA
实例,然后创建
ThingA1
ThingA2
的实例(以及
ThingA
Thing
,但此处未显示)

现在的关系是这样的(
\uuuu proto\uuu
是一个内部属性,将对象与其原型连接起来):

因此,
ThingA
ThingA1
ThingA2
的每个实例都引用一个相同的数组实例

这不是你想要的


解决方案 为了解决这个问题,任何“子类”的每个实例都应该有自己的
relatedThings
属性。您可以通过在每个子构造函数中调用父构造函数来实现这一点,类似于在其他语言中调用
super()

function ThingA() {
    Thing.call(this);
}

function ThingA1() {
    ThingA.call(this);
}

// ...
这将调用
Thing
ThingA
,并将这些函数中的
This
设置为传递给
的第一个参数。call
。了解更多关于和的信息

仅此一项即可将上述图片更改为:

                               +-------------+
                               |   ThingA    |
                               |             |    
+-------------+                |  prototype  |----+
|   ThingA1   |                +-------------+    |
|             |                                   |
|  prototype  |---> +--------------+              |
+-------------+     |    ThingA    |              |
                    | instance (1) |              |
                    |              |              |
                    | relatedThings|---> Array    |
+-------------+     |  __proto__   |--------------+ 
|   ThingA1   |     +--------------+              |
|   instance  |           ^                       |
|             |           |                       |
|relatedThings|---> Array |                       v
|  __proto__  |-----------+                 +--------------+
+-------------+                             |Thing instance|
                                            |              |
                                            | relatedThings|---> Array
+-------------+     +--------------+        +--------------+ 
|   ThingA2   |     |   ThingA     |              ^
|             |     | instance (2) |              |
|  prototype  |---> |              |              |
+-------------+     | relatedThings|---> Array    |
                    |  __proto__   |--------------+
                    +--------------+
+-------------+           ^
|   ThingA2   |           |  
|   instance  |           |
|             |           |
|relatedThings|---> Array | 
|  __proto__  |-----------+
+-------------+
如您所见,每个实例都有自己的
relatedThings
属性,它引用不同的数组实例。原型链中仍然有
relatedThings
属性,但它们都被实例属性隐藏


更好的继承 此外,不要将原型设置为:

ThingA.prototype = new Thing();
实际上,您不想在这里创建一个新的
实例。如果
Thing
预期参数会发生什么?你会通过哪一个?如果调用
东西
构造函数有副作用怎么办

实际上,您需要将
Thing.prototype
连接到原型链中。您可以通过以下方式执行此操作:


当构造函数(
Thing
)被执行时发生的任何事情都将在稍后发生,当我们实际创建一个新的
ThingA
实例时(通过调用
Thing.call(this)
,如上所示)。

在OOP中,当您定义子类的构造函数时,您也是(隐式或显式的)从超级类型中选择构造函数。当一个子对象被构造时,两个构造函数都被执行,首先是来自超类的构造函数,然后是层次结构中的所有其他构造函数

在javascript中,这必须显式调用,而不是自动调用

function Thing() {
  this.relatedThings = [];
}
Thing.prototype.relateThing = function(what){
  this.relatedThings.push(what);
}

function ThingA(){
  Thing.call(this);
}
ThingA.prototype = new Thing();

function ThingA1(){
  ThingA.call(this);
}
ThingA1.prototype = new ThingA();

function ThingA2(){
  ThingA.call(this);

}
ThingA2.prototype = new ThingA();
如果不这样做,则ThingA、ThingA1和ThingA2的所有实例都是在Thing构造函数中构造的相同relatedThings数组,并在第行中对所有实例调用一次:

ThingA.prototype = new Thing();

事实上,在您的代码中,您只有2次调用Thing构造函数,这只会导致relatedThings数组的2个实例。

如果您不喜欢JavaScript中的原型工作方式,以实现简单的继承和OOP方式,我建议您看一下:

它基本上允许您执行以下操作(以及更多操作):


JavaScript基本上是基于对象的,那么为什么它不应该是面向对象的呢?就我对JavaScript的了解而言,原型语言与面向对象语言并不完全相同(没有类等),尽管概念可能类似。但是我不是一个专家,事实上JavaScript是非常面向对象的,但它的工作方式与其他OO语言不同。事实上,继承是不受支持的。许多库实现了一些高级的方法,这些方法实际上是解决继承问题的一种方法。目前我发现的最好的一个是John Resig的,它基于原型和base2库,但进一步改进了。您的代码是否未按预期工作?不知道你想要什么结果,你得到了什么instead@Joel_Blum执行之后,所有对象在各自的relatedThings数组中都有一个“hello”。我原以为只有thingList[1]会有它。我会停止写我要写的东西。可靠的反应+1@Felix:令人印象深刻的响应,我正在测试。还有一个问题:我改变了设置原型的方式,但构造函数的指定方式是否应该与我的相同?i、 e:ThingA.prototype.constructor=ThingA@内韦伯
function Thing() {
  this.relatedThings = [];
}
Thing.prototype.relateThing = function(what){
  this.relatedThings.push(what);
}

function ThingA(){
  Thing.call(this);
}
ThingA.prototype = new Thing();

function ThingA1(){
  ThingA.call(this);
}
ThingA1.prototype = new ThingA();

function ThingA2(){
  ThingA.call(this);

}
ThingA2.prototype = new ThingA();
ThingA.prototype = new Thing();
// First (bottom level)
var Person = new Class(function() {
    this.name = "Unknown Person";
});

// Employee, extend on Person & apply the Role property.
var Employee = new Class({ extends: Person }, function() {
    this.name = 'Unknown Employee';
    this.role = 'Employee';
});

// 3rd level, extend on Employee. Modify existing properties.
var Manager = new Class({ extends: Employee }, function() {

    // Overwrite the value of 'role'.
    this.role = 'Manager';

    // Class constructor to apply the given 'name' value.
    this.__construct = function(name) {
        this.name = name;
    }
});

// And to use the final result:
var myManager = new Manager("John Smith");
console.log( myManager.name ); // John Smith
console.log( myManager.role ); // Manager