了解JavaScript中的原型继承

我是JavaScript OOP的新手。 你能解释下面的代码块之间的区别吗? 我测试过,并且两个块都可以工作 最佳做法是什么?为什么?

第一块:

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

Car.prototype.Drive = function(){
    document.write("My name is " + this.Name + " and I'm driving. <br />");
}

SuperCar.prototype = new Car();
SuperCar.prototype.constructor = SuperCar;

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

SuperCar.prototype.Fly = function(){
    document.write("My name is " + this.Name + " and I'm flying! <br />");
}

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(){ 
        document.write("My name is " + this.Name + " and I'm driving. <br />");
    }
}

SuperCar.prototype = new Car();

function SuperCar(name){
    Car.call(this, name);
    this.Fly = function(){
        document.write("My name is " + this.Name + " and I'm flying! <br />");
    }
}

var myCar = new Car("Car");
myCar.Drive();

var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();

为什么作者使用prototype添加DriveFly方法,但是并没有将它们声明为Car类中的this.Drive方法, this.FlySuperCar类中使用this.Fly

为什么SuperCar.prototype.constructor需要重新设置为SuperCarprototype设置时是否覆盖构造函数属性? 我评论了这条线,没有任何改变。

为什么叫Car.call(this, name);SuperCar构造函数中? 当我这样做的时候Car属性和方法不会被“继承”

var myCar = new Car("Car");

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

JavaScript中的new调用自动将构造函数设置为原型。 如果您覆盖原型,则必须手动设置构造函数。

在JavaScript中的继承没有像super 。 所以如果你有一个子类,唯一的机会就是调用超级构造函数的名字。


要添加到Norbert Hartl的答案中,SuperCar.prototype.constructor不是必需的,但有些人使用它作为获取对象构造函数的便捷方式(本例中为SuperCar对象)。

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

var mySuperCar = new SuperCar("SuperCar");

这就是JavaScript所做的:

  • 一个新鲜的空白对象被实例化。
  • 新对象的内部原型设置为Car。
  • SuperCar构造函数运行。
  • 完成的对象返回并设置在mySuperCar中。
  • 注意JavaScript没有给你打电话。 原型就像它们一样,任何你不为SuperCar设置的属性或方法都将在Car中查找。 有时候这很好,例如SuperCar没有Drive方法,但它可以共享Car的方法,因此所有的SuperCar都将使用相同的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);
    }
    

    作为一个功能,汽车被允许与函数的调用方法,它改变的意义被调用this车内我们打造了超级跑车的实例。 普雷斯托! 现在每个SuperCar都获得它自己的Name属性。

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

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


    诺伯特,你应该注意到,你的第一个例子几乎就是道格拉斯克罗克福德所称的伪古典继承。 需要注意的是:

  • 你会从SuperCar.prototype = new Car()行中调用两次Car构造函数,另一次从“构造函数窃取”行Car.call中调用一次(这...你可以创建一个辅助方法来继承原型,而你的汽车构造商只需要运行一次,使安装更有效率。
  • SuperCar.prototype.constructor = SuperCar行将允许您使用instanceof来标识构造函数。 有些人希望这个其他人只是避免使用instanceof
  • 参考变量如:var arr = ['one','two']在super(例如Car)上定义时将被所有实例共享。 这意味着inst1.arr.push ['three'],inst2.arr.push ['four']等将显示所有实例! 本质上,你可能不想要的静态行为。
  • 第二个块在构造函数中定义了fly方法。 这意味着每次调用它时,都会创建一个“方法对象”。 更好地使用方法的原型! 你可以将它保留在构造函数中,如果你愿意的话 - 你只需要保护,所以你只需要实际初始化原型文件(伪):if(SuperCar.prototype.myMethod!='function')...然后定义你的原型文字。
  • '为什么要调用Car.call(这个,名字)....':我没有时间仔细查看你的代码,所以我可能是错的,但通常这样每个实例都可以保持它自己的状态来修复我上面描述的原型链的'staticy'行为问题。
  • 最后,我想提一下,我在这里有几个TDD JavaScript继承代码的例子:TDD JavaScript继承代码和论文我希望得到您的反馈,因为我希望改进它并保持开源。 我们的目标是帮助古典程序员快速掌握JavaScript,并补充Crockford和Zakas书籍的研究。

    链接地址: http://www.djcxy.com/p/30051.html

    上一篇: Understanding prototypal inheritance in JavaScript

    下一篇: Use of 'prototype' vs. 'this' in JavaScript?