Bird.prototype = Animal.prototype; // 解决方案3
由于这将Animal.prototype
直接赋值给Bird.prototype
,因此您对后者所做的所有更改也会反映在Animal
和所有继承自它的其他类中。
例如:
Bird.prototype.fly = function() {
console.log('I can fly.');
}
var some_animal = new Animal();
some_animal.fly();
这肯定不是你想要的。这个子类的每个方法不仅对其他子类可用,而且如果你在子类中重写父类方法,最后定义的子类将覆盖其他子类的更改。
在你的问题标题中,你似乎把它称为“原型副本”。这是不正确的,因为没有创建副本。 Animal.prototype
和 Bird.prototype
将引用同一个对象。
Bird.prototype = new Animal(); // 解决方案2
这是设置继承的常见方式,但有两个主要缺点:
- 如果
Animal
需要参数,该如何传递?传递随意的、无意义的参数只是为了使函数调用起作用似乎是不好的设计。
Animal
可能有副作用,比如增加实例计数器,但此时你实际上并没有创建一个新实例(或者不想创建),你只是想设置继承关系。
Bird.prototype = Object.create(Animal.prototype); // 解决方案1
这是现在应该采用的方式(直到有更好的解决方案出现)。你真正想做的是把Animal
的原型连接到 Bird
的原型链中,这样做可以避免之前解决方案的所有缺点。
但是为了使其正常工作,你还必须在Bird
构造函数内调用Animal
构造函数。就像其他编程语言中的调用 super
一样:
function Bird(){
Animal.call(this);
}
这确保对一个新的 Animal
实例所做的任何命令/更改都将应用于新的 Bird
实例。
同时,将原型的 constructor
属性赋予正确的值也是一个好习惯。它通常指向原型“所属的”函数,但在上述所有解决方案中,Bird.prototype.constructor
将引用 Animal
。因此,我们需要进行以下操作:
Bird.prototype.constructor = Bird
该属性在内部不被使用,因此只有当您自己的代码依赖于它时才需要这样做。然而,如果您创建一个供他人使用的库,他们可能会期望该属性具有正确的值。
constructor
属性总是从原型继承的。它不是实例本身的属性。 - Felix KlingBird.prototype = Object.create(Animal.prototype);
的更改将影响到Animal.prototype
属性。我建议使用 jQuery extendBird.prototype = jQuery.extend( true, {}, Animal.prototype );
,它可以提供精确的副本。 - rabFoo
也可以执行一些非常复杂的初始化,这很难复制,或者你可能根本不知道Foo
在做什么(例如内置构造函数,如Array
)。再次强调,你可以随心所欲地做任何事情。我只是说做正确的事情是最正确的。 - Felix Kling