在JavaScript中安全地继承原型

在JavaScript中安全地继承原型,javascript,inheritance,prototype,prototypal-inheritance,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, argume

假设我在应用程序中尝试一些基本的继承,我可以通过将我孩子的原型设置为父对象来实现这一点

// 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
上设置一个新方法,它将在
汽车的原型上设置

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

myCar.stop(); // stooop  <--- bad
myToyota.stop(); // stooop  <--- good
但我不明白为什么会这样
ctor
创建一个新实例,将其原型设置为
汽车的
原型,然后
Toyota
将其原型设置为该新实例

但是为什么要创建一个带有原型的空对象,该原型由
Toyota
的原型引用呢?
为什么不在
丰田
上设置一个新方法,像在第一个示例中那样在
汽车
上设置它呢?
如果我想要几个继承层,似乎每次都需要用
新的ctor
将它们粘在一起?

您的问题如下:

Toyota.prototype=Car.prototype

然后稍后修改此对象:

Toyota.prototype.stop = function() {
    console.log('stooop');
};
因为在第一行中,您将
Toyota.prototype
设置为与
Car.prototype
完全相同的对象。这不是副本,它是对同一对象的引用!因此,只要您在
Toyota.prototype
上修改
stop
,您实际上就同时修改了
Toyota.prototype
Car.prototype
,因为这是一个相同的版本

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

Toyota.prototype=Object.create(汽车)

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

为了进一步讨论这里到底发生了什么,以及为什么不在JS中使用“类继承”更好,您可能需要阅读

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

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

因为如果你这样做:
Toyota.prototype=newcar()

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

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

Toyota.prototype = new ctor();
现在丰田.prototype拥有
新的ctor()
实例作为它的原型,而它自己的链中又有
汽车.prototype
。这意味着丰田的实例现在可以执行Car.prototype中存在的方法


为什么不在丰田上设置一个新方法,像在汽车上那样设置它呢 在第一个例子中

这样:
Toyota.prototype=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最好不要使用创建父对象的实例来设置为子对象的原型。父对象可能具有不应位于Child.prototype上的实例特定成员,或者在定义子对象时,无法创建父对象的实例,因为它取决于应用程序在定义子对象时尚未达到的特定应用程序状态。改为使用Object.create。最好不要使用创建父对象的实例来设置为子对象的原型。父对象可能具有不应位于Child.prototype上的实例特定成员,或者在定义子对象时,无法创建父对象的实例,因为它取决于应用程序在定义子对象时尚未达到的特定应用程序状态。改为使用Object.create。最好不要使用这些“类”结构,而是在所有位置使用
Object.create
。但事实确实如此。所以你建议不要使用构造函数?无论如何;你是从道格拉斯·克罗克福德那里得到的吗?你知道他还没有为“经典继承”产生正确的代码吗?您将如何初始化特定于实例的成员,以及构造函数到底有什么不好的地方;但是不,我没有从克罗克福德那里得到这个,我甚至没有读过他的书。你可以阅读一下为什么JS中的类继承是次优的。谢谢你的回复。仍然没有得到使用第6章模式而不是构造函数的好处。其他
myToyota.stop(); //=> 'stooop'
myCar.stop(); //=> 'TypeError: undefined is not a function'

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