在JavaScript中安全地继承原型

时间:2014-10-27 20:02:33

标签: javascript inheritance prototype prototypal-inheritance

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

// 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将它们粘在一起?

2 个答案:

答案 0 :(得分:2)

  

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

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

现在丰田的原型是 Car 的一个实例,这意味着除了 Car &#39;在链中的原型,它还具有在 Car 构造函数本身中设置的任何属性。基本上你不希望 Toyota.prototype 拥有一个名为year的属性,如:Toyota.prototype.year。因此,拥有像这样的空构造函数要好得多:

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

Toyota.prototype = new ctor();

现在 Toyota.prototype new ctor()个实例作为原型,而后者又有Car.prototype个链。这意味着丰田的实例现在可以执行 Car.prototype 中存在的方法。


  

为什么不在丰田上设置新方法,将其设置为 Car ,就像它一样   在第一个例子中?

有了这个:Toyota.prototype = Car.prototype;你要设置丰田&#39;原型与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

答案 1 :(得分:1)

您的问题如下:

Toyota.prototype = Car.prototype;

然后修改此对象:

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

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

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

Toyota.prototype = Object.create(Car);

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

有关这里正在发生的事情的进一步讨论,以及为什么你可能会更好地而不是使用&#34;类继承&#34;在JS中,你可能想阅读Chapters 4-6 of You Don't Know JS (objects & prototypes)

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