Javascript“OOP”和具有多级继承的原型
Posted
技术标签:
【中文标题】Javascript“OOP”和具有多级继承的原型【英文标题】:Javascript "OOP" and prototypes with multiple-level inheritance 【发布时间】:2013-02-09 01:27:58 【问题描述】:我是 javascript 编程的新手,我正在从面向对象编程的角度来处理我的第一个应用程序(实际上是一个游戏)(我知道 js 并不是真正面向对象的,但对于这个特殊问题,这对我来说更容易像这样开始)。
我有一个“类”层次结构,其中最顶层(“事物”类)定义了相关事物的列表(游戏中的附加项目)。它由 ThingA1 和 ThingA2 类继承的 ThingA 类继承。
最简单的例子是:
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');
在代码的最后,当执行关联事物时,每个 ThingA、ThingA1 和 ThingA2 都将执行它(不是数组中的最后一个“Thing”对象)。我发现如果我在 ThingA 原型中定义了相关函数,它将正常工作。由于游戏的设计方式,我宁愿不必这样做。
也许我不了解原型如何在 javascript 中工作。我知道该功能在所有对象之间共享,但我想执行将是单独的。有人可以解释为什么会发生这种情况以及如何解决它吗?我不知道是我做错了继承,或者原型定义,还是什么。
提前致谢。
【问题讨论】:
JavaScript 几乎都是基于对象的,为什么不应该是面向对象的呢? 据我所知,原型语言与面向对象的语言并不完全相同(没有类等),尽管概念可能相似。但我不是专家。 JavaScript 强烈面向对象这一事实并不意味着它的工作方式与其他 OO 语言相同。事实上,继承是不支持的。许多库实现了高级方法,这些方法确实是改进继承工作方式的解决方法。目前我发现的最好的之一是 John Resig 的,它基于 Prototype 和 base2 库,但进一步改进。 ejohn.org/blog/simple-javascript-inheritance 您的代码没有按预期工作吗?不明白你想要什么结果和你得到了什么 @Joel_Blum 执行后,所有对象在它们各自的relatedThings 数组中都有一个“hello”。我原以为只有 thingList[1] 会拥有它。 【参考方案1】:欢迎来到原型链!
让我们看看它在您的示例中是什么样子的。
问题
当您调用new Thing()
时,您正在创建一个具有属性relatedThings
的新对象,该属性引用一个数组。所以我们可以说我们有这个:
+--------------+
|Thing instance|
| |
| relatedThings|----> Array
+--------------+
然后您将此实例分配给ThingA.prototype
:
+--------------+
| ThingA | +--------------+
| | |Thing instance|
| prototype |----> | |
+--------------+ | relatedThings|----> Array
+--------------+
所以ThingA
的每个实例都将继承自Thing
实例。现在您将创建ThingA1
和ThingA2
并为它们的每个原型分配一个新的ThingA
实例,然后创建ThingA1
和ThingA2
的实例(以及ThingA
和Thing
,但是此处未显示)。
现在的关系是这样的(__proto__
是一个内部属性,连接一个对象和它的原型):
+-------------+
| ThingA |
| |
+-------------+ | prototype |----+
| ThingA1 | +-------------+ |
| | |
| prototype |---> +--------------+ |
+-------------+ | ThingA | |
| instance (1) | |
| | |
+-------------+ | __proto__ |--------------+
| ThingA1 | +--------------+ |
| instance | ^ |
| | | v
| __proto__ |-----------+ +--------------+
+-------------+ |Thing instance|
| |
| relatedThings|---> Array
+-------------+ +--------------+ +--------------+
| ThingA2 | | ThingA | ^
| | | instance (2) | |
| prototype |---> | | |
+-------------+ | __proto__ |--------------+
+--------------+
+-------------+ ^
| ThingA2 | |
| instance | |
| | |
| __proto__ |-----------+
+-------------+
因此,ThingA
、ThingA1
或 ThingA2
的每个实例指的是同一个数组实例。
这不是你想要的!
解决方案
为了解决这个问题,任何“子类”的每个实例都应该有自己的relatedThings
属性。您可以通过在每个子构造函数中调用父构造函数来实现这一点,类似于在其他语言中调用super()
:
function ThingA()
Thing.call(this);
function ThingA1()
ThingA.call(this);
// ...
这会调用Thing
和ThingA
并将这些函数内的this
设置为您传递给.call
的第一个参数。详细了解.call
[MDN] 和this
[MDN]。
仅此一项就会将上图变为:
+-------------+
| 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
预期的参数会发生什么?你会通过哪一个?如果调用Thing
构造函数有副作用怎么办?
您实际上想要的是将Thing.prototype
连接到原型链中。你可以通过Object.create
[MDN] 做到这一点:
ThingA.prototype = Object.create(Thing.prototype);
在执行构造函数 (Thing
) 时发生的任何事情都会在稍后发生,当我们实际创建一个新的 ThingA
实例时(通过调用 Thing.call(this)
,如上所示)。
【讨论】:
好吧。我会停止写我要写的东西。坚定的回应。 +1 @Felix :令人印象深刻的反应,我现在正在测试。只是另一个问题:我改变了我设置原型的方式,但应该以与我相同的方式指定构造函数吗?即:ThingA.prototype.constructor = ThingA; @neverbot:嗯,我希望它有帮助,也许它太混乱了:D 设置constructor
不是必需,因为没有内部方法使用它,但它是好的做法。
很好的解释。 +1 的努力。
漂亮的图表。对于最后一部分,在What is the reason (not) to use the 'new' keyword here? 有一些很好的答案。【参考方案2】:
如果您不喜欢 JavaScript 中的原型设计方式以实现简单的继承和 OOP,我建议您看一下:https://github.com/haroldiedema/joii
它基本上允许您执行以下(以及更多)操作:
// 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
【讨论】:
【参考方案3】:在 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 构造函数,这导致只有 2 个 relatedThings 数组实例。
【讨论】:
@Felix Kling:感谢您的建议,我已更正。我也读了你的,恭喜:)以上是关于Javascript“OOP”和具有多级继承的原型的主要内容,如果未能解决你的问题,请参考以下文章
玩转JavaScript OOP[4]——实现继承的12种套路
javaScript设计模式之面向对象编程(object-oriented programming,OOP)--寄生组合式继承