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