假设我在我的应用程序中进行了一些基本的继承,我可以通过将我的孩子的原型设置为父级来实现。
// 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
将它们粘在一起?
答案 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.prototype
和Car.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中(假)类和继承的标准模式。