了解JavaScript中的原型继承

Das*_*alo 170 javascript oop inheritance constructor prototype-programming

我是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();
Run Code Online (Sandbox Code Playgroud)

第二块:

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();
Run Code Online (Sandbox Code Playgroud)

为什么笔者的增加DriveFly方法使用prototype,并没有宣布他们的this.Drive内部方法Car类和this.FlySuperCar类?

为什么SuperCar.prototype.constructor需要重新开始SuperCar?设置constructorprototype是否覆盖了属性?我评论了这一行并没有改变.

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

var myCar = new Car("Car");
Run Code Online (Sandbox Code Playgroud)

Tun*_*yen 140

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

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

var mySuperCar = new SuperCar("SuperCar");
Run Code Online (Sandbox Code Playgroud)

这就是JavaScript的作用:

  1. 一个新的空白对象被实例化.
  2. 新鲜物体的内部原型设置为Car.
  3. SuperCar构造函数运行.
  4. 返回完成的对象并在mySuperCar中设置.

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

function SuperCar(name){
    this.Name = name;
}
Run Code Online (Sandbox Code Playgroud)

这有效,但等一下.我们不是在Car构造函数中做同样的事情吗?不想重复自己.由于Car已经设置了名称,我们只需要调用它.

function SuperCar(name){
    this = Car(name);
}
Run Code Online (Sandbox Code Playgroud)

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

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

function SuperCar(name){
    Car.call(this, name);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 如果你想在传统引擎中模拟`Object.getPrototypeOf`工作,那么需要"SuperCar.prototype.constructor" (4认同)
  • 目前链接到"Javascript中的原型概念"已被打破.还有其他地方我可以阅读这篇文章吗? (3认同)

Nor*_*rtl 82

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

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

javascript中的继承没有什么比这更好的了super.因此,如果你有一个子类,那么调用超级构造函数的唯一机会就是它的名字.


Rob*_*Rob 8

诺伯特,你应该注意到你的第一个例子就是道格拉斯·克罗克福德所谓的伪经典继承.关于此事需要注意的事项:

  1. 您将调用Car构造函数两次,一次来自SuperCar.prototype = new Car()行,另一次来自"构造函数窃取"行Car.call(这...你可以创建一个帮助方法继承原型而不是你的汽车构造函数只需运行一次,使设置更有效.
  2. SuperCar.prototype.constructor = SuperCar行将允许您使用instanceof来标识构造函数.有些人希望其他人只是避免使用instanceof
  3. 参考变量如:var arr = ['one','two']在超级(例如Car)上定义时将被所有实例共享.这意味着inst1.arr.push ['three'],inst2.arr.push ['four']等将出现在所有实例中!基本上,您可能不想要的静态行为.
  4. 第二个块在构造函数中定义了fly方法.这意味着每次调用它时,都会创建一个"方法对象".最好使用原型方法!但是如果你愿意,你可以将它保存在构造函数中 - 你只需要保护,这样你只需要初始化一次原型文字(伪):if(SuperCar.prototype.myMethod!='function')...然后定义你的原型文字.
  5. '为什么要调用Car.call(这个,名字)....':我没有时间仔细查看你的代码,所以我可能错了,但通常这样每个实例都可以保持自己的状态来修复我在上面描述的原型链的'staticy'行为问题.

最后,我想提一下我在这里有几个TDD JavaScript继承代码的例子:TDD JavaScript继承代码和论文我很想得到你的反馈,因为我希望改进它并保持开源.目标是帮助经典程序员快速掌握JavaScript,并补充Crockford和Zakas书籍的研究.