原型复制 vs Object.create() vs new

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

如果我注释掉它们所有,确实会出现错误。

如果我只取消注释其中一个,程序就能正常工作。

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

2个回答

14

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(); // this will work

这肯定不是你想要的。这个子类的每个方法不仅对其他子类可用,而且如果你在子类中重写父类方法,最后定义的子类将覆盖其他子类的更改。

在你的问题标题中,你似乎把它称为“原型副本”。这是不正确的,因为没有创建副本。 Animal.prototypeBird.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;

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


1
@Rikonator:除非您的代码依赖于它,否则您不必这样做。它在内部未被使用,因此不是必需的。尽管我认为这是一个好习惯。将进行更新。 - Felix Kling
@ghost23:不,constructor属性总是从原型继承的。它不是实例本身的属性。 - Felix Kling
@FelixKling .. 对于 Bird.prototype = Object.create(Animal.prototype); 的更改将影响到 Animal.prototype 属性。我建议使用 jQuery extend Bird.prototype = jQuery.extend( true, {}, Animal.prototype );,它可以提供精确的副本。 - rab
@Felix Kling 我对此持怀疑态度。做了一个快速的示例:http://jsfiddle.net/aJ4fx/ - ghost23
@AvinashAgrawal:如果你只对辅助函数感兴趣,那就是你的特定用例。如果你知道不必调用父构造函数,那就不要调用。我在谈论一般情况。我的例子很简单。Foo也可以执行一些非常复杂的初始化,这很难复制,或者你可能根本不知道Foo在做什么(例如内置构造函数,如Array)。再次强调,你可以随心所欲地做任何事情。我只是说做正确的事情是最正确的。 - Felix Kling
显示剩余12条评论

0

你可以使用解决方案一解决方案二,但是不应该使用解决方案三

原因是当你执行Bird.prototype = Animal.prototype;时,对于Bird.prototype的任何更改都会影响到Animal.prototype,因为它们是同一个对象。

例如,你在Bird.prototype上附加一个方法,那么这个方法也将作用于Animal上,这显然不是你想要的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接