【问题标题】:Safely inheriting prototypes in JavaScript [duplicate]在 JavaScript 中安全地继承原型 [重复]
【发布时间】:2014-10-27 20:02:33
【问题描述】:

假设我正在为我的应用程序中的一些基本继承进行拍摄,我可以通过将我的孩子的原型设置为父母来实现这一点。

// Parent "class"
var Car = function(year) {
    this.year = year;
};

Car.prototype.drive = function() {
    console.log('Vrooom');
};

// Child "class"
var Toyota = function() {
    Car.apply(this, arguments);
};

Toyota.prototype = Car.prototype;

var myCar = new Car(2010), myToyota = new Toyota(2010);

myCar.drive(); // Vrooom
myToyota.drive(); // Vrooom

似乎可行,但显然很糟糕,因为如果我在 Toyota.prototype 上设置新方法,它将在 Car 的原型上设置。

Toyota.prototype.stop = function() {
    console.log('stooop');
};

myCar.stop(); // stooop  <--- bad
myToyota.stop(); // stooop  <--- good

为了解决这个问题,我可以添加一个中介来代替Toyota.prototype = Car.prototype;

var ctor = function() { };
ctor.prototype = Car.prototype;

Toyota.prototype = new ctor();

Toyota.prototype.stop = function() {
    console.log('stooop');
};

myCar.stop(); // throws undefined error  <--- good
myToyota.stop(); // stooop  <--- good

但我不明白为什么会这样。 ctor 创建一个新实例,其原型设置为Car's 原型,然后Toyota 将其原型设置为该新实例。

但是为什么要创建一个空对象,其原型会被Toyota 的原型引用?
为什么不像在第一个示例中那样在Toyota 上设置新方法在Car 上设置它? 如果我想要几层继承怎么办,似乎我每次都需要用new ctor将它们粘合在一起?

【问题讨论】:

  • 也许下面的答案会对你有所帮助,它涵盖了构造函数、原型、继承和混入:stackoverflow.com/a/16063711/1641941
  • @spender 你的意思是Toyota.prototype = Object.create(Car.prototype) 不是吗?最好不要创建 Parent 的实例用作 Child 的原型。
  • @HMR:是的,我删除了我的答案,因为很明显我对 JS 继承的了解有些缺陷。也忘记删除评论了。
  • @HMR 很棒的帖子,感谢您写下它并指向它。

标签: javascript inheritance prototype prototypal-inheritance


【解决方案1】:

但是为什么要创建一个带有被引用的原型的空对象 丰田的原型?

因为如果你这样做:Toyota.prototype = new Car();

现在Toyota的原型是Car的一个实例,这意味着除了链中有Car的原型,它还具有在 Car 构造函数本身中设置的任何属性。基本上,您不希望 Toyota.prototype 有一个名为 year 的属性,如下所示:Toyota.prototype.year。正因为如此,最好有一个像这样的空构造函数:

var ctor = function() { };
ctor.prototype = Car.prototype;

Toyota.prototype = new ctor();

现在 Toyota.prototypenew ctor() 实例作为它的原型,而 Car.prototype 在它自己的链中。这意味着 Toyota 的实例现在可以执行存在于 Car.prototype 中的方法。


为什么在 Toyota 上设置新方法不像在 Car 上那样设置 在第一个例子中?

有了这个:Toyota.prototype = Car.prototype;,您将 Toyota 的原型设置为与 Car.prototype 包含的完全相同的对象。由于它是同一个对象,因此在一个地方更改它也会在其他任何地方更改它。这意味着对象是通过引用而不是通过值传递的,这是另一种说法,当您将一个对象分配给 3 个不同的变量时,无论您使用哪个变量,它都是同一个对象。例如,字符串是按值传递的。下面是一个字符串示例:

var str1 = 'alpha';
var str2 = str1;
var str3 = str1;

str2 = 'beta';

// Changing str2 doesn't change the rest.
console.log(str1); //=> "alpha"
console.log(str3); //=> "alpha"
console.log(str2); //=> "beta"

现在比较对象及其属性;

var obj1 = {name: 'joe doe'};
var obj2 = obj1;
var obj3 = obj1;

console.log(obj1.name); //=> "joe doe"
console.log(obj2.name); //=> "joe doe"
console.log(obj3.name); //=> "joe doe"

obj2.name = 'JOHN SMITH';

console.log(obj1.name); //=> "JOHN SMITH"
console.log(obj2.name); //=> "JOHN SMITH"
console.log(obj3.name); //=> "JOHN SMITH"


如果我想要多层继承怎么办...

这是另一种创建继承层的方法:

var Car = function(year) {
    this.year = year;
};

Car.prototype.drive = function() {
    console.log('Vrooom');
};

var Toyota = function() {
    Car.apply(this, arguments);
};

// `Object.create` does basically the same thing as what you were doing with `ctor()`
// Check out the documentation for `Object.create` as it takes a 2nd argument too.
Toyota.prototype = Object.create(Car.prototype);


// Create instances
var 
  myCar = new Car(2010), 
  myToyota = new Toyota(2010);


// Add method to Toyota's prototype
Toyota.prototype.stop = function() {
    console.log('stooop');
};

现在就来试试吧:

myToyota.stop(); //=> 'stooop'
myCar.stop(); //=> 'TypeError: undefined is not a function'

myCar.drive(); //=> Vrooom
myToyota.drive(); //=> Vrooom

【讨论】:

  • 谢谢!这一切都说得通,你是唯一一个不告诉我将其设置为 new Car() 的实例的人:D
  • 我删除了对此的回答... +1
  • 最好不要使用创建Parent的实例来设置为Child的原型。 Parent 可能具有不应在 Child.prototype 上的实例特定成员,或者当您定义 Child 时,您无法创建 Parent 实例,因为它取决于应用程序尚未达到的某个应用程序状态定义孩子。请改用 Object.create。
【解决方案2】:

您的问题是以下行:

Toyota.prototype = Car.prototype;

然后再修改这个对象:

Toyota.prototype.stop = function() {
    console.log('stooop');
};

因为在第一行中,您将Toyota.prototype 设置为与Car.prototype 完全相同的对象。 这不是一个副本,它是对同一个对象的引用! 所以只要你修改了Toyota.prototype 上的stop,你实际上同时修改了Toyota.prototypeCar.prototype,因为它是一回事。

您真正想做的是将第一行替换为:

Toyota.prototype = Object.create(Car);

这样你现在就有了Toyota 的原型指向Car 函数,而不是Car 自己的prototype。恭喜,你已经使用了原型链! (注意:使用Object.create进行类继承本质上更稳定可靠,因为它不会运行您可能包含在Car函数中的任何代码,而只是设置原型链接。)

要进一步讨论这里到底发生了什么,以及为什么在 JS 中完全使用“类继承”会更好,你可能想阅读Chapters 4-6 of You Don't Know JS (objects & prototypes)

关于您的最后一个问题:“如果我想要几层继承怎么办,似乎我每次都需要将它们与一个新的 ctor 粘合在一起?” – 是的,你需要这样做,这是 JavaScript 中(假)类和继承的标准模式。

【讨论】:

  • 最好不要使用创建Parent的实例来设置为Child的原型。 Parent 可能具有不应在 Child.prototype 上的实例特定成员,或者当您定义 Child 时,您无法创建 Parent 实例,因为它取决于应用程序尚未达到的某个应用程序状态定义孩子。请改用 Object.create。
  • 最好不要使用这些“类”结构,在所有地方都使用Object.create。但这确实是真的。
  • 所以你建议不要使用构造函数?在任何情况下;你是从道格拉斯·克罗克福德那里得到的吗?你知道他还没有为“经典继承”产生正确的代码吗?您将如何初始化实例特定成员?构造函数到底有什么不好?
  • 这个讨论不属于这里;但是不,我不是从 Crockford 那里得到的,我什至没有读过他的书。您可以在 YDKJS Chapters 4-6 中了解为什么 JS 中的类继承不是最理想的。
  • 感谢您的回复。仍然没有得到使用第 6 章模式而不是构造函数的好处。除了比混淆构造函数更喜欢语法之外,我看不到明显的技术优势。
猜你喜欢
  • 2019-01-24
  • 2016-07-04
  • 2020-12-28
  • 1970-01-01
  • 2016-11-19
  • 1970-01-01
  • 2015-10-14
相关资源
最近更新 更多