5

我正在使用继承,我注意到有三种方法可以获得相同的结果。有什么不同?

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() 可见。

如果我评论所有这些,则确实存在错误。

如果我只删除其中一个,程序就可以工作。

那么......这三种解决方案之间的真正区别是什么?

4

2 回答 2

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可能会有副作用,例如增加实例计数器,但此时您实际上并没有创建新实例(或不想),您只是想设置继承。

Bird.prototype = Object.create(Animal.prototype); // Solution 1

这就是你现在应该这样做的方式(直到有更好的解决方案出现)。你真正想做的就是Animal将原型连接到Bird. 它避免了以前解决方案的所有缺点。

但是,要使其正常工作,您还必须在构造函数中调用Animal构造Bird函数。这就像super在其他编程语言中的调用:

function Bird(){
    Animal.call(this);
}

这确保对新实例所做的任何命令/更改Animal都应用于新Bird实例。

constructor为原型的属性分配正确的值也是一种很好的做法。它通常指原型“所属”的功能,但在上述所有解决方案中,Bird.prototype.constructor都会指Animal. 所以我们必须这样做:

Bird.prototype.constructor = Bird;

此属性在内部不使用,因此只有在您自己的代码依赖它时才需要这样做但是,如果您创建一个供其他人使用的库,他们可能会期望该属性必须正确的值。

于 2013-04-19T08:46:02.823 回答
0

您可以使用解决方案 1解决方案 2,但不应使用解决方案 3

原因是当你这样做的时候Bird.prototype = Animal.prototype;,每一次改变Bird.prototype都会影响到Animal.prototype,因为它们是同一个对象。

例如,您在 上附加了一个方法Bird.prototype,那么该方法也将适用于Animal,这不是您想要的。

于 2013-04-19T08:39:44.340 回答