2

来自 codeacademy 的练习:

function Penguin(name) {
    this.name = "Pingy";
    this.numLegs = 2;
}

// create your Emperor class here and make it inherit from Penguin
function Emperor (name){
    this.name = name;
}
Emperor.prototype = new Penguin();
var emp = new Emperor("Empy");

所以我Emperor从 继承属性Penguin,现在我知道那emp.numLegs将是 2。

阅读评论后,我编辑了问题:如您所见,我在创建时给了一个名称emp,并且emp.name确实是我的新 emp 的名称,即“Empy”。但是name继承自 Penguin 类的构造函数呢?它去哪儿了?

4

4 回答 4

8

Codecademy 是初学者学习 JavaScript 的好方法。学习任何编程语言都需要练习,而 Codecademy 让你练习。

但是,有时您需要超越实践的范围并学习一些理论。阅读以下答案。它很好地解释了 JavaScript 中的原型继承:https ://stackoverflow.com/a/8096017/783743

现在您有一个Penguin构造函数,如下所示:

function Penguin(name) {
    this.name = name;
    this.numLegs = 2;
}

然后你创建一个Emperor继承自的构造函数Penguin

function Emperor(name) {
    this.name = name;
}

Emperor.prototype = new Penguin();

请注意,您正在创建一个实例Penguin并将其分配给Emperor.prototype. 因此:

  1. Emperor.prototype.name = undefined- 这是因为您没有将任何内容传递namePenguin构造函数。如果我写了,Emperor.prototype = new Penguin("Empy")Emperor.prototype.name就是"Empy"
  2. Emperor.prototype.numLegs = 2- 明显地。

现在,当您按如下方式创建新的时Emperor,您需要为构造函数命名(请记住,您是作为名称继承的)undefinedPenguin因此:

var emp = new Emperor("Empy");

那是老派的方法。

如今,JavaScript 程序员使用Object.createcall从另一个构造函数继承。让我用一个例子来解释:

function Emperor(name) {
    Penguin.call(this, name);
}

Emperor.prototype = Object.create(Penguin.prototype);
Emperor.prototype.constructor = Emperor;

与老式方法相比,这种方法有几个优点:

  1. 我们继承了using的原型成员,而不是创建Penguinusing的新实例new Penguin()并且不向其传递任何参数。这还可以防止在派生构造函数被实际调用之前对基构造函数进行不必要的初始化。PenguinObject.create(Penguin.prototype)
  2. 我们没有再次编写this.name = name派生构造函数,而是使用Penguin.call(this, name)它为我们调用基构造函数。这种模式称为 mixin,如果需要在运行时初始化基本构造函数或需要维护自己的状态信息,则该模式非常有用。

请注意,我们还添加了一个附加语句Emperor.prototype.constructor = Emperor。这是因为prototype它是一个非常特殊的属性,存在于所有函数中。它指向一个具有非常特殊constructor属性的对象,该属性指向函数本身。通过设置Emperor.prototype别的东西,我们失去了这个属性。因此我们再次设置它。

这两个示例中的净效果是相同的。然而,对于更复杂的代码,使用新方法要好得多。快乐学习 JavaScript。

于 2013-03-17T14:03:44.857 回答
5

我假设您想知道为什么必须在构造函数中和构造函数内部提供name参数。Emperorthis.name = name;

JavaScript 中的原型继承非常简单,没有隐藏的魔法。Emperor您在和之间提供的唯一连接Penguin是在这一行:

Emperor.prototype = new Penguin();

Emperor.prototype现在是 的一个实例Penguin,但它可以是任何对象。该函数 Emperor对该函数一无所知Penguin,因此它不会神奇地调用Penguin

当你用 调用函数 ( Func) 时new,它所做的只是创建一个新的空对象,其原型是函数prototype属性 ( Func.prototype)。该对象将成为this构造函数内部的值,如果没有返回其他对象,将隐式返回。

但是从 Penguin 类构造函数继承的名称呢?它去哪儿了?

它是每个实例的属性的一个属性,Emperor.prototype并被每个Emperor实例的name属性所遮蔽。

看看我的回答,我在这里创建了一些很好的 ASCII 图表来解释实例和它们的原型之间的关系。


注意: Emperor.prototype = new Penguin();实际上不是建立继承的好方法。如果Penguin 需要 参数会发生什么?什么会通过?

此时你其实并不想创建 的新实例Penguin,只想将Penguin'挂接prototype到原型链中。您可以通过以下方式轻松做到这一点Object.create

Emperor.prototype = Object.create(Penguin.prototype);
Emperor.prototype.constructor = Emperor;

但随后新Emperor对象将不再具有numLegs属性。这就是为什么您必须Penguin在每个新Emperor实例上调用 ,就像在其他语言中一样super()

function Emperor (name){
    Penguin.call(this, name);
} 
于 2013-03-17T13:45:30.597 回答
1

调用时new Penguin(),此方法将创建一个对象并返回该对象的原型。

在进行原型继承时,您唯一要做的就是Penguin首先创建一个新对象并将这些值放入Emperor. 现在,当您创建一个新Emperor对象时,原型已经设置为Penguin. 要覆盖这些,您需要在新的构造函数中再次设置它们。

JavaScript 中的继承与其他语言非常不同。

代码示例:

function Penguin(name) {
    this.name = name;
    this.legs = 2;
}

function Emperor(name) {
    this.name = name;
}
Emperor.prototype = new Penguin();
/**
 * The prototype of Emperor now contains:
 * 
       {
           "name": undefined,
           "legs": 2
       }
 * 
 * Because you didn't add an argument to the constructor of `Penguin`
 */

var e = new Emperor('foo');
// The name property will get overriden with 'foo'
于 2013-03-17T13:52:21.130 回答
1

Penguin对象在创建时也需要将名称传递给name参数。因此,当您创建类型的新对象时,Penguin您必须传递名称。同样,由于Emperor继承自Penguin对象,因此您必须在构造函数中传递值,以便如果您希望执行与基类中发生的处理不同的处理,Penguin您可以在子类构造函数中定义它。但Emperor可以是任何类型的对象对象,因为它没有关于定义的信息Penguin

如果在“Emperor”类的定义中删除该行

 this.name = name;

然后你可以在构造函数中传递任何变量,仍然得到超级构造函数中定义的值“Pingy”

如果您添加该行

 Penguin.call(this, name) ;// As to call super class instance

然后你得到正确传递给新对象的值numLegsas 2

于 2013-03-17T13:53:32.967 回答