我是JavaScript OOP的新手。你能解释下面的代码块之间的区别吗?我测试了两个块都有效。什么是最佳实践,为什么?
第一块:
function Car(name){
this.Name = name;
}
Car.prototype.Drive = function(){
console.log("My name is " + this.Name + " and I'm driving.");
}
SuperCar.prototype = new Car();
SuperCar.prototype.constructor = SuperCar;
function SuperCar(name){
Car.call(this, name);
}
SuperCar.prototype.Fly = function(){
console.log("My name is " + this.Name + " and I'm flying!");
}
var myCar = new Car("Car");
myCar.Drive();
var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();
第二块:
function Car(name){
this.Name = name;
this.Drive = function(){
console.log("My name is " + this.Name + " and I'm driving.");
}
}
SuperCar.prototype = new Car();
function SuperCar(name){
Car.call(this, name);
this.Fly = function(){
console.log("My name is " + this.Name + " and I'm flying!");
}
}
var myCar = new Car("Car");
myCar.Drive();
var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();
为什么作者使用Drive
添加Fly
和prototype
方法,并没有在this.Drive
类中将它们声明为Car
方法,而在this.Fly
类中将它们声明为SuperCar
?
为什么SuperCar.prototype.constructor
需要回到SuperCar
?当constructor
被设置时,prototype
属性是否被覆盖?我评论了这一行并没有改变。
为什么在Car.call(this, name);
构造函数中调用SuperCar
?当我这样做时,Car
的属性和方法不会被“继承”
var myCar = new Car("Car");
这两个方块的区别在于,在第一个例子中,Drive()
只存在一次,而在第二个方法中,Drive()
将存在于每个实例中(每次你执行new Car()
时,将再次创建函数drive()
)。或者说不同的是第一个使用原型来存储函数而第二个使用构造函数。函数的查找是构造函数,然后是原型。因此,对于Drive()
的查找,无论它是在构造函数中还是在原型中,它都会找到它。使用原型更有效,因为通常每种类型只需要一次函数。
javascript中的new
调用会自动在原型中设置构造函数。如果要覆盖原型,则必须手动设置构造函数。
javascript中的继承与super
完全不同。因此,如果你有一个子类,那么调用超级构造函数的唯一机会就是它的名字。
要添加到Norbert Hartl's answer,不需要SuperCar.prototype.constructor,但是有些人使用它作为获取对象的构造函数的便捷方式(在这种情况下为SuperCar对象)。
从第一个例子开始,Car.call(this,name)在SuperCar构造函数中,因为当你这样做时:
var mySuperCar = new SuperCar("SuperCar");
这就是JavaScript的作用:
请注意JavaScript没有为您调用Car。原型就像它们一样,你没有为SuperCar设置的任何属性或方法都将在Car中查找。有时这很好,例如SuperCar没有Drive方法,但它可以共享Car的方法,因此所有SuperCars都将使用相同的Drive方法。其他时候你不想分享,比如每个SuperCar都有自己的名字。那么如何将每个SuperCar的名称设置为它自己的东西呢?你可以在SuperCar构造函数中设置this.Name:
function SuperCar(name){
this.Name = name;
}
这有效,但等一下。我们不是在Car构造函数中做同样的事情吗?不想重复自己。由于Car已经设置了名称,我们只需要调用它。
function SuperCar(name){
this = Car(name);
}
哎呀,你永远不想改变特殊的this
对象参考。还记得4个步骤吗?挂在JavaScript给你的对象上,因为这是保持SuperCar对象和Car之间珍贵的内部原型链接的唯一方法。那么我们如何设置Name,而不是重复自己并且不丢弃我们新鲜的SuperCar对象JavaScript花了这么多特别的努力为我们做准备?
两件事情。一:this
的含义是灵活的。二:汽车是一种功能。可以调用Car,而不是使用原始的,新鲜的实例化对象,而是使用SuperCar对象。这为我们提供了最终解决方案,这是您问题中第一个示例的一部分:
function SuperCar(name){
Car.call(this, name);
}
作为一个函数,允许使用函数call method调用Car,它将Car中的this
的含义更改为我们正在构建的SuperCar实例。普雷斯托!现在每个SuperCar都有自己的Name属性。
总结一下,在SuperCar构造函数中的Car.call(this, name)
为每个新的SuperCar对象提供了它自己唯一的Name属性,但没有重复已经在Car中的代码。
一旦你理解了它们,原型并不可怕,但它们根本不像经典的类/继承OOP模型。我写了一篇关于the prototypes concept in JavaScript的文章。它是为使用JavaScript的游戏引擎编写的,但它与Firefox使用的JavaScript引擎相同,因此它应该都是相关的。希望这可以帮助。
诺伯特,你应该注意到你的第一个例子就是道格拉斯·克罗克福德所谓的伪经典继承。关于此事需要注意的事项:
最后,我想提一下我在这里有几个TDD JavaScript继承代码的例子:TDD JavaScript Inheritance Code and Essay我很想得到你的反馈,因为我希望改进它并保持开源。目标是帮助经典程序员快速掌握JavaScript,并补充Crockford和Zakas书籍的研究。
我不是100%肯定,但我相信不同之处在于第二个示例只是将Car类的内容复制到SuperCar对象中,而第一个示例将SuperCar原型链接到Car类,因此运行时更改为Car类也会影响SuperCar类。
function abc() {
}
为函数abc创建的原型方法和属性
abc.prototype.testProperty = 'Hi, I am prototype property';
abc.prototype.testMethod = function() {
alert('Hi i am prototype method')
}
为函数abc创建新实例
var objx = new abc();
console.log(objx.testProperty); // will display Hi, I am prototype property
objx.testMethod();// alert Hi i am prototype method
var objy = new abc();
console.log(objy.testProperty); //will display Hi, I am prototype property
objy.testProperty = Hi, I am over-ridden prototype property
console.log(objy.testProperty); //will display Hi, I am over-ridden prototype property
http://astutejs.blogspot.in/2015/10/javascript-prototype-is-easy.html
这里有几个问题:
能否请您解释以下代码块之间的区别。我测试了两个块都有效。
第一个只创建一个Drive
函数,第二个创建其中两个:一个在myCar
而另一个在mySuperCar
。
下面是执行第一个或第二个块时会产生不同结果的代码:
myCar.Fly === mySuperCar.Fly // true only in the first case
Object.keys(myCar).includes("Fly") // true only in the second case
Object.keys(Car.prototype).length === 0 // true only in the second case
什么是最佳实践,为什么? 为什么作者使用
Drive
添加Fly
和prototype
方法,但是在this.Drive
类中没有将它们声明为Car
类和this.Fly
类中的SuperCar
方法?
最好在原型上定义方法,因为:
Object.create(Car.prototype)
时就是这种情况);为什么
SuperCar.prototype.constructor
需要回到SuperCar
?当constructor
被设置时,prototype
属性是否被覆盖?我评论了这一行并没有改变。
设置constructor
时,不会覆盖prototype
属性。但是new Car()
的构造函数是Car
,所以如果你将new Car()
设置为SuperCar.prototype
,那么显然SuperCar.prototype.constructor
就是Car
。
只要你不重新分配给prototype
,就会有一个不变性:Constructor.prototype.constructor === Constructor
。例如,Car
:Car.prototype.constructor === Car
就是如此,但对Array
,Object
,String
等等也是如此。
但是如果你将另一个物体重新分配给prototype
,那么这种不变性就会被打破。通常这不是问题(正如您所注意到的),但最好还原它,因为它回答了“在创建新实例时哪个构造函数使用此原型对象?”的问题。有些代码可能会进行此类检查并依赖于此。有关此类案件,请参阅"Why is it necessary to set the prototype constructor?"。
为什么在
Car.call(this, name);
构造函数中调用SuperCar
?当我这样做时,Car的属性和方法不会被“继承”var myCar = new Car("Car");
如果你不做Car.call(this, name);
那么你的SuperCar
实例将没有name属性。您当然可以决定只改变this.name = name;
,它只是复制Car
构造函数中的代码,但在更复杂的情况下,这样的代码重复将是不好的做法。
在new Car(name)
构造函数中调用SuperCar
是没有用的,因为这将创建另一个对象,而你真的需要扩展this
对象。通过不使用new
(使用call
)你实际上告诉Car
函数不作为构造函数运行(即不创建新对象),而是使用传递给它的对象。
在现代版本的JavaScript中,您可以使用super(name)
而不是Car.call(this, name)
:
function SuperCar(name) {
super(name);
}
今天,您还将使用class
语法并从问题中编写第一个代码块,如下所示:
class Car {
constructor(name) {
this.name = name;
}
drive() {
console.log(`My name is ${this.name} and I'm driving.`);
}
}
class SuperCar extends Car {
constructor(name) {
super(name);
}
fly() {
console.log(`My name is ${this.name} and I'm flying!`);
}
}
const myCar = new Car("Car");
myCar.drive();
const mySuperCar = new SuperCar("SuperCar");
mySuperCar.drive();
mySuperCar.fly();
请注意,您甚至不必提及prototype
属性来实现目标。 class ... extends
语法还负责将prototype.constructor
属性设置为您问题中的第一个块。