ChatGPT解决这个技术问题 Extra ChatGPT

了解 JavaScript 中的原型继承

我是 JavaScript OOP 的新手。您能解释一下以下代码块之间的区别吗?我进行了测试,两个块都可以工作。最佳做法是什么,为什么?

第一块:

功能汽车(名称){ this.Name = name; } Car.prototype.Drive = function(){ console.log("我的名字是 " + this.Name + " 我正在开车。"); } SuperCar.prototype = new Car(); SuperCar.prototype.constructor = SuperCar;功能 SuperCar(name){ Car.call(this, name); } SuperCar.prototype.Fly = function(){ console.log("我的名字是 " + this.Name + " 我在飞!"); } var myCar = new Car("Car"); myCar.Drive(); var mySuperCar = new SuperCar("SuperCar"); mySuperCar.Drive(); mySuperCar.Fly();

第二块:

功能汽车(名称){ this.Name = name; this.Drive = function(){ console.log("我的名字是 " + this.Name + " 我正在开车。"); } } SuperCar.prototype = new Car();功能 SuperCar(name){ Car.call(this, name); this.Fly = function(){ console.log("我的名字是 " + this.Name + " 我在飞!"); } } 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 属性?我注释掉了这一行,没有任何改变。

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

var myCar = new Car("Car");
更现代的方法是使用 Object.create() 而不是 new:ncombo.wordpress.com/2013/07/11/…
请注意,此示例可能会造成混淆:本例中的“super”指的是飞行汽车,而 OOP 中的“super”通常指的是父原型/类。
我认为使 JavaScript 的原型继承难以理解的部分是它与构造函数混合的地方。如果我们去掉构造函数和类的想法,我们只有对象。每个对象要么从无(null)继承,要么从另一个对象继承。这将更容易理解,而这正是 Object.create() 所做的。

C
Community

要添加到 Norbert Hartl's answer,不需要 SuperCar.prototype.constructor,但有些人将其用作获取对象(在本例中为 SuperCar 对象)的构造函数的便捷方式。

仅从第一个示例开始, Car.call(this, name) 就在 SuperCar 构造函数中,因为当您执行此操作时:

var mySuperCar = new SuperCar("SuperCar");

这就是 JavaScript 所做的:

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

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

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

这有效,但请稍等。我们不是在 Car 构造函数中做了完全相同的事情吗?不想重蹈覆辙。由于 Car 已经设置了名称,我们就直接调用它吧。

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

糟糕,您永远不想更改特殊的 this 对象引用。还记得这4个步骤吗?抓住 JavaScript 给你的那个对象,因为它是保持 SuperCar 对象和 Car 之间宝贵的内部原型链接的唯一方法。那么我们如何设置名称,而不重复我们自己,也不会丢弃我们新鲜的 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 引擎相同,因此应该都是相关的。希望这可以帮助。


如果您希望在旧引擎中模拟 Object.getPrototypeOf 工作,则需要“SuperCar.prototype.constructor”
当前指向“Javascript 中的原型概念”的链接已损坏。还有其他地方可以阅读这篇文章吗?
“原型就是这样,您没有为 SuperCar 设置的任何属性或方法都将在 Car 中查找”。如果不调用基础对象构造函数,我不明白派生对象如何从基础对象获取方法和属性。派生对象原型是否查看基础对象并添加派生对象没有的所有内容?还是基础对象原型具有基础对象的方法和属性?
m
mik01aj

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

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

javascript 中的继承与 super 完全不同。因此,如果您有一个子类,那么调用超级构造函数的唯一机会就是通过它的名称。


R
Rob

Norbert,您应该注意,您的第一个示例几乎就是 Douglas Crockford 所说的伪经典继承。对此有几点需要注意:

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

最后,我想提一下,我有几个适用于此处的 TDD JavaScript 继承代码示例:TDD JavaScript Inheritance Code and Essay我很想得到您的反馈,因为我希望改进它并保持开源。目标是帮助经典程序员快速上手 JavaScript,并补充 Crockford 和 Zakas 书籍的学习。


我之前遇到过第(1)点。我发现如果不使用 new 关键字而只使用 Superclass.call(this) (所以 SuperCar = { Car.call(this); }; SuperCar.prototype = Car; )这似乎有效。有理由不这样做吗? (为死灵道歉)
来自经典 OOP 语言,我总是发现奇怪需要创建一个new实例只是来填充原型链
好书。您应该注意到 github 页面顶部的链接已损坏,当然,它可以在内容中找到。
J
Jeff Ober

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


J
Jack Sane
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


t
trincot

这里有几个问题:

您能否解释以下代码块之间的区别。我进行了测试,两个块都可以工作。

第一个只创建一个 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方法,却没有在Car类中声明为this.Drive方法,在SuperCar类中声明为this.Fly?

在原型上定义方法是更好的做法,因为:

每个方法只定义一次

每个方法也可用于未执行构造函数而创建的实例(调用 Object.create(Car.prototype) 时就是这种情况);

您可以检查在实例的原型链中的哪个级别定义了某个方法。

为什么需要将 SuperCar.prototype.constructor 设置回 SuperCar?设置原型时是否会覆盖构造函数属性?我注释掉了这一行,没有任何改变。

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

只要您不重新分配给 prototype,就会有一个不变性:Constructor.prototype.constructor === Constructor。例如,这对于 Car: Car.prototype.constructor === Car 是正确的,但对于 ArrayObjectString、...等也是如此。

但是,如果您将不同的对象重新分配给 prototype,那么这种不变性就会被破坏。通常这不是问题(正如您已经注意到的),但最好恢复它,因为它回答了“哪个构造函数在创建新实例时使用这个原型对象?”的问题一些代码可能会这样做这样的检查并依赖它。有关此类情况,请参见 "Why is it necessary to set the prototype constructor?"

为什么叫 Car.call(this, name);在 SuperCar 构造函数中?当我做 var myCar = new Car("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 语法并编写问题的第一个代码块,如下所示:

类汽车 { 构造函数(名称) { this.name = name; } drive() { console.log(`我的名字是 ${this.name} 我正在开车。`); } } 类 SuperCar 扩展 Car { 构造函数(名称) { 超级(名称); } fly() { console.log(`我的名字是 ${this.name} 我在飞!`); } } const myCar = new Car("Car"); myCar.drive(); const mySuperCar = new SuperCar("SuperCar"); mySuperCar.drive(); mySuperCar.fly();

请注意,您甚至不必提及 prototype 属性即可实现目标。 class ... extends 语法还负责设置 prototype.constructor 属性,就像您问题中的第一个块所做的那样。