来自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”。但是从Penguin类构造函数继承的name
呢?它去哪儿了?
答案 0 :(得分: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
。因此:
Emperor.prototype.name = undefined
- 这是因为您没有将任何name
传递给Penguin
构造函数。如果我写了Emperor.prototype = new Penguin("Empy")
,那么Emperor.prototype.name
就是"Empy"
。Emperor.prototype.numLegs = 2
- 显然。现在,当您按如下所示创建新的Emperor
时,您需要为构造函数指定一个名称(请记住,您从undefined
继承了Penguin
作为名称)。因此:
var emp = new Emperor("Empy");
这是旧学校的方法。
现在JavaScript程序员使用Object.create
和call
继承另一个构造函数。让我用一个例子来解释:
function Emperor(name) {
Penguin.call(this, name);
}
Emperor.prototype = Object.create(Penguin.prototype);
Emperor.prototype.constructor = Emperor;
这种方法比旧学校方法有几个优点:
Penguin
继承new Penguin()
的原型成员,而不是使用Penguin
创建Object.create(Penguin.prototype)
的新实例并且不向其传递任何参数。这也可以防止在实际调用派生构造函数之前对基础构造函数进行不必要的初始化。this.name = name
,而是使用Penguin.call(this, name)
为我们调用基础构造函数。这种模式称为mixin,如果基本构造函数需要在运行时初始化或需要维护自己的状态信息,则非常有用。请注意,我们还添加了一个附加声明Emperor.prototype.constructor = Emperor
。这是因为prototype
是一个非常特殊的属性,存在于所有函数中。它指向一个具有非常特殊的constructor
属性的对象,该属性指向函数本身。通过将Emperor.prototype
设置为其他内容,我们会失去此属性。因此我们再次设置它。
这两个例子的净效果是相同的。但是对于更复杂的代码,使用新方法要好得多。快乐学习JavaScript。
答案 1 :(得分:5)
我假设您想知道为什么必须在构造函数中为name
和Emperor
提供this.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
属性遮蔽。
看看my answer here我在哪里创建了一些很好的ASCII图表来解释实例与它们的原型之间的关系。
注意: Emperor.prototype = new Penguin();
实际上不是建立继承的好方法。如果Penguin
需要参数,会发生什么?什么会通过?
此时您实际上不想创建Penguin
的新实例,您只想将Penguin
的{{1}}连接到原型链中。您可以使用prototype
轻松完成此操作:
Object.create
但是新的Emperor.prototype = Object.create(Penguin.prototype);
Emperor.prototype.constructor = Emperor;
对象不再具有Emperor
属性。这就是为什么你必须在每个新的numLegs
实例上调用Penguin
,就像使用Emperor
的其他语言一样:
super()
答案 2 :(得分:1)
调用new Penguin()
时,此方法将创建一个对象并返回该对象的原型。
在进行原型保留时,您唯一要做的就是首先创建一个新的Penguin
对象,然后将这些值放在Emperor
的原型中。现在,当您创建新的Emperor
对象时,原型已设置为Penguin
的值。要覆盖它们,您需要在新的构造函数中再次设置它们。
JavaScript中的Inhertiance与其他语言非常不同。
代码示例:
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'
答案 3 :(得分:1)
创建Penguin
对象时,还需要将名称传递给name
参数。因此,当您创建类型为Penguin
的新对象时,您必须传递名称。类似地,因为Emperor
继承自Penguin
对象,您必须在构造函数中传递值,这样如果您希望执行与基类Penguin
中发生的处理不同的处理,则可以在子类构造函数中定义它。但是Emperor
可以是任何类型的对象对象,因为它没有关于Penguin
定义的信息。
如果在“Emperor”类的定义中删除了行
this.name = name;
然后你可以在构造函数中传递任何变量,仍然得到超级构造函数中定义的值“Pingy”
如果添加行
Penguin.call(this, name) ;// As to call super class instance
然后,您将值正确传递到新对象上,同时numLegs
为