了解 JavaScript 中的原型继承

我是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();

为什么作者使用 和 添加方法,而没有将它们声明为类内和类中的方法?DriveFlyprototypethis.DriveCarthis.FlySuperCar

为什么需要设置回 ?设置时属性是否被覆盖?我注释掉了这句话,什么也没改变。SuperCar.prototype.constructorSuperCarconstructorprototype

为什么要调用构造函数?不会的属性和方法被“继承”,当我这样做Car.call(this, name);SuperCarCar

var myCar = new Car("Car");

答案 1

为了补充Norbert Hartl的答案,SuperCar.prototype.constructor不是必需的,但有些人使用它作为获取对象(在这种情况下是SuperCar对象)构造函数的便捷方式。

从第一个例子来看,Car.call(this,name)位于SuperCar构造函数中,因为当你这样做时:

var mySuperCar = new SuperCar("SuperCar");

这就是JavaScript所做的:

  1. 将实例化一个全新的空白对象。
  2. 新对象的内部原型设置为 Car。
  3. 超级跑车构造函数运行。
  4. 完成的对象将被返回并在mySuperCar中设置。

请注意,JavaScript 没有为你调用 Car。原型是这样的,任何你没有为超级跑车设置的属性或方法都将在Car中查找。有时这很好,例如SuperCar没有Drive方法,但它可以共享Car的方法,因此所有SuperCar都将使用相同的Drive方法。其他时候你不想分享,比如每辆超级跑车都有自己的名字。那么,如何将每辆超级跑车的名字都设置为它自己的东西呢?您可以设置它。超级跑车构造函数内部的名称:

function SuperCar(name){
    this.Name = name;
}

这有效,但请稍等片刻。我们在汽车构造器中不是做了完全相同的事情吗?不想重复自己。由于Car已经设置了名称,因此让我们直接调用它。

function SuperCar(name){
    this = Car(name);
}

哎呀,你永远不想改变特殊对象引用。还记得这4个步骤吗?抓住JavaScript给你的那个对象,因为这是在你的SuperCar对象和Car之间保持宝贵的内部原型链接的唯一方法。那么,我们如何设置 Name,而不重复自己,不丢弃我们新的 SuperCar 对象,JavaScript 花了这么多特别的精力来为我们做准备呢?this

两件事。一:寓意是灵活。二:汽车是一个功能。可以调用 Car,不是使用原始的、新鲜的实例化对象,而是使用 SuperCar 对象。这给了我们最终的解决方案,这是您问题中第一个示例的一部分:this

function SuperCar(name){
    Car.call(this, name);
}

作为一个函数,允许使用函数的调用方法调用Car,这会将Car内部的含义更改为我们正在构建的SuperCar实例。普雷斯托!现在,每辆超级跑车都有自己的 Name 属性。this

总而言之,在 SuperCar 构造函数中,为每个新的 SuperCar 对象提供它自己唯一的 Name 属性,但不复制 Car 中已有的代码。Car.call(this, name)

一旦你理解了原型,它们就不那么可怕了,但它们与经典的类/继承OOP模型完全不同。我写了一篇关于JavaScript中原型概念的文章。它是为使用JavaScript的游戏引擎编写的,但它与Firefox使用的JavaScript引擎相同,因此它应该都是相关的。希望这有帮助。


答案 2

这两个块的不同之处在于,在第一个示例中,每个实例只存在一次,而在第二个示例中,每个实例都将存在(每次执行该函数时,将再次创建)。或者不同的是,第一个使用原型来存储函数,第二个使用构造函数。函数的查找是构造函数,然后是原型。因此,对于您的查找,无论它是在构造函数中还是在原型中,都可以找到它。使用原型更有效,因为通常每个类型只需要一个函数一次。Drive()Drive()new Car()drive()Drive()

javascript 中的调用会自动设置原型中的构造函数。如果要覆盖原型,则必须手动设置构造函数。new

JavaScript 中的继承与 .因此,如果你有一个子类,那么调用超级构造函数的唯一机会就是通过其名称。super