我正在使用继承,我注意到有三种方法可以获得相同的结果。有什么区别?
function Animal(){
}
Animal.prototype.doThat = function() {
document.write("Doing that");
}
function Bird(){
}
// This makes doThat() visible
Bird.prototype = Object.create(Animal.prototype); // Solution 1
// You can also do:
// Bird.prototype = new Animal(); // Solution 2
// Or:
// Bird.prototype = Animal.prototype; // Solution 3
var myVar = new Bird();
myVar.doThat();
如你所见,我提出了三个解决方案。它们中的每一个都使方法doThat()可见。
如果我评论所有这些,确实存在错误。
如果我只解除其中一个程序,那么程序就可以运行。
那么......三种解决方案之间真正区别的是什么?
答案 0 :(得分:13)
Bird.prototype = Animal.prototype; // Solution 3
由于这已将Animal.prototype
直接分配给Bird.prototype
,因此您对后者所做的所有更改也会反映在Animal
以及从中继承的所有其他类中。
例如:
Bird.prototype.fly = function() {
console.log('I can fly.');
}
var some_animal = new Animal();
some_animal.fly(); // this will work
这肯定不是你想要的。此子类的每个方法不仅可用于其他子类,而且如果覆盖子类中的父方法,则最后定义的子类将覆盖其他子类的更改。
在您的问题标题中,您似乎将此称为“原型副本”。这是不正确的,因为没有创建副本。 Animal.prototype
和Bird.prototype
将引用同一个对象。
Bird.prototype = new Animal(); // Solution 2
这是设置继承很长时间的常用方法,但它有两个主要缺点:
Animal
需要参数,您通过了哪些参数?传递随机,毫无意义的参数只是为了使函数调用工作似乎是糟糕的设计。Animal
可能有副作用,比如增加一个实例计数器,但是在这一刻你实际上没有创建一个新实例(或者不想),你只想设置继承。 / LI>
Bird.prototype = Object.create(Animal.prototype); // Solution 1
这就是你现在应该这样做的方式(until a better solution comes)。你真正想做的就是将Animal
的原型连接到Bird
的原型链中。它避免了以前解决方案的所有缺点。
为了使其正常工作,您还必须在Animal
构造函数中调用Bird
构造函数。这就像在其他编程语言中调用super
一样:
function Bird(){
Animal.call(this);
}
这可确保对新Animal
实例所做的任何命令/更改都应用于新的Bird
实例。
将正确的值分配给原型的constructor
属性也是一种好习惯。它通常是指原型“属于”的函数,但在上述所有解决方案中,Bird.prototype.constructor
将引用Animal
。所以我们必须这样做:
Bird.prototype.constructor = Bird;
此属性未在内部使用,因此如果您自己的代码依赖它,则必须执行此操作。但是,如果您创建一个供其他人使用的库,他们可能希望该属性必须更正值。
答案 1 :(得分:0)
您可以使用解决方案1 或解决方案2 ,但不应使用解决方案3 。< / p>
原因是当您执行Bird.prototype = Animal.prototype;
时,Bird.prototype
上的每次更改也会影响Animal.prototype
,因为它们是同一个对象。
例如,您在Bird.prototype
上附加了一个方法,然后该方法也适用于Animal
,这将不是您想要的。