繁体   English   中英

了解JavaScript中的原型继承

[英]Understanding prototypal inheritance in 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(); 

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

为什么需要将SuperCar.prototype.constructor设置回SuperCar 设置prototype时是否重写constructor属性? 我评论了这一行并没有改变。

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

var myCar = new Car("Car");

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

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

var mySuperCar = new SuperCar("SuperCar");

这就是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;
}

这有效,但等一下。 我们不是在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引擎相同,因此它应该都是相关的。 希望这可以帮助。

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

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

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

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

  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书籍的研究。

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

我不是100%肯定,但我相信不同之处在于第二个示例只是将Car类的内容复制到SuperCar对象中,而第一个示例将SuperCar原型链接到Car类,因此运行时更改为Car类也会影响SuperCar类。

这里有几个问题:

能否请您解释以下代码块之间的区别。 我测试了两个块都有效。

第一个只创建一个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

什么是最佳实践,为什么?
为什么作者使用prototype添加DriveFly方法,但是没有将它们声明为Car类中的this.Drive方法和SuperCar类中的this.Fly

最好在原型上定义方法,因为:

  • 每个方法只定义一次
  • 每个方法也可用于在不执行构造函数的情况下创建的实例(调用Object.create(Car.prototype)时就是这种情况);
  • 您可以检查实例原型链中的哪个级别定义了某个方法。

为什么需要将SuperCar.prototype.constructor设置回SuperCar 设置prototype时是否重写constructor属性? 我评论了这一行并没有改变。

设置prototype时,不会覆盖constructor属性。 但是new Car()的构造函数是Car ,所以如果你将new Car()设置为SuperCar.prototype ,那么显然SuperCar.prototype.constructor就是Car

只要你不重新分配prototype ,就有一个不变性: Constructor.prototype.constructor === Constructor 例如,对于CarCar.prototype.constructor === Car ,这是正确的,但对于ArrayObjectString等等同样如此。

但是如果你将一个不同的对象重新分配给prototype ,那么这种不变性就会被打破。 通常这不是问题(正如您所注意到的),但最好还原它,因为它回答了“在创建新实例时哪个构造函数使用此原型对象?”的问题。 有些代码可能会进行此类检查并依赖于此。 请参阅“为什么有必要设置原型构造函数?” 对于这种情况。

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

 var myCar = new Car("Car"); 

如果你不做Car.call(this, name); 那么你的SuperCar实例将没有name属性。 你当然可以决定这样做this.name = name; 相反,它只是复制Car构造函数中的代码,但在更复杂的情况下,这样的代码重复将是不好的做法。

SuperCar构造函数中调用new Car(name)会没有用,因为这会创建另一个对象,而你真的需要扩展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属性设置为你问题中的第一个块。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM