【问题标题】:JavaScript Inheritance - Am I doing it right?JavaScript 继承 - 我做对了吗?
【发布时间】:2012-12-09 18:12:51
【问题描述】:

问题:

  1. 为什么第一种方法能正确地将对象识别为法拉利? (在 Chrome 中测试)
  2. 我在这两种方法中的继承是否正确?
  3. 我是否正确理解第一个示例中有一些隐藏的“构造函数”属性,如果是,它们在哪里,是什么把它们放在那里?在我没有明确输入的第二个示例中是否有任何隐藏的构造函数属性?
  4. 如果我已经正确地完成了继承,我应该使用最后两种方法中的哪一种来让对象被识别为法拉利?还是我应该不在乎 - 毕竟“instanceof”仍然有效?

上下文:

使用 JavaScript 的“新”方法创建对象,效果很好:

Vehicle.prototype = {}; // Not required I know
function Vehicle() {

}

Car.prototype = new Vehicle();
function Car() {

}

Ferrari.prototype = new Car();
function Ferrari() {

}
var o = new Ferrari();
console.log(o);

哪些输出:

> Ferrari
  > __proto__ : Car
    > __proto__ : Vehicle
      > __proto__ : Object <- The one that isn't required
        > __proto__ : Object <- Provided by JS
          ...
          hasOwnProperty: function hasOwnProperty() { [native code] }
          ...

现在我想做同样的事情,避免使用 new 关键字,这就是我所拥有的:

Vehicle.prototype = {};
function Vehicle() {
  var vehicle = Object.create(Vehicle.prototype);
  return vehicle;
}

Car.prototype = Vehicle();
Car.prototype.constructor = Vehicle;
function Car() {
  var car = Object.create(Car.prototype);
  return car;
}

Ferrari.prototype = Car();
Ferrari.prototype.constructor = Car;

function Ferrari() {
  var ferrari = Object.create(Ferrari.prototype);
  //ferrari.constructor = Ferrari;  <- Lookey here - commented line
  return ferrari;
}

var o = new Ferrari();
console.log(o);

哪些输出:

> **Car**
  > __proto__ : Car
    > constructor : function Car()
      __proto__ : Vehicle
      > constructor : function Vehicle()
        __proto__ : Object <- The one that isn't required
        > __proto__ : Object
          ...
          hasOwnProperty: function hasOwnProperty() { [native code] }
          ...

请注意,输出的第一行现在是 Car,而不是 Ferrari。这可以通过删除注释行来纠正,或者通过改变法拉利的原型如下:

var realPrototype = Car();
realPrototype.constructor = Car;

Ferrari.prototype = Object.create(realPrototype);
Ferrari.prototype.constructor = Ferrari;

【问题讨论】:

  • 您将Ferrari.prototype.constructor 设置为Car。但是重置constructor 的重点是Ferrari 的实例将其构造函数设置回Ferrari 而不是Car(覆盖prototype 时会自动发生)。
  • @pimvdb 我没有这样做的原因是因为在第二种方法中(在对象图中)法拉利的原型是法拉利,而实际上它是“实例一辆车”。有趣的是,当你对第一种方法做同样的事情时,它根本不会改变图表! - 不一致向我表明这不是正确的继承方式?
  • @Lee 这种继承方式很好,你只需要将每个子类型的构造函数设置为父类型。您可以省点麻烦并在函数体内执行此操作,也可以在每次为子类型制作原型时重置构造函数。看我的回答。还有,o的原型是FerrariFerrari的原型不是Ferrari

标签: javascript inheritance prototype


【解决方案1】:

为什么第一种方法能正确识别出对象为法拉利? (在 Chrome 中测试)

我是否正确理解第一个示例中有一些隐藏的“构造函数”属性,如果是,它们在哪里,是什么把它们放在那里?在我没有明确输入的第二个示例中是否有任何隐藏的构造函数属性?

是的,如果您分配给函数的 prototype 属性或以某种方式创建新实例,Chrome 似乎设置了一个隐藏的“构造函数”属性。具体还没有弄清楚。

他们在哪里?可能只有console.log 可以访问的一些内部属性。但是,您似乎可以使用显式的“构造函数”属性覆盖它们,至少在显示 __proto__ 对象时是这样。

我猜这是由 Chrome 完成的,因为许多人在覆盖原型对象时忘记设置构造函数属性(因为 it's actually useless),尽管它是 is recommended - 另见 JavaScript inheritance and the constructor property。 Chrome 开发者想要制作一个漂亮的 log 函数来指示对象的类型,因此他们需要解决人们的懒惰问题。

我在这两种方法中的继承是否正确?

您的代码中的继承没有问题。新创建的Car 实例具有期望的原型链,具有两种方法。

不过,也有一些陷阱。不要使用 new 关键字来创建原型 objects 实例。您不想要完整的实例,并在它们上应用构造函数。只需使用Car.prototype = Object.create(Vehicle.prototype);另见What is the reason to use the 'new' keyword at Derived.prototype = new Base上的答案

另外,你没有设置“构造函数”属性,或者做错了。在原型对象上定义的该属性由new 调用相应构造函数创建的所有对象继承,并且它应该为它们提供对该函数的引用。对于任何创建的函数,默认情况下都会这样做,请参阅EcmaScript specification §13.2 steps 16-18

如果您重新分配给 fn.prototype,您将覆盖该行为(无论它是对象文字、new 构造还是 Object.create 调用)。实际上,“构造函数”属性是 inherited from an object higher up in the prototype chain - 在您的第一个示例中,o.constructorObject,继承自空对象文字。因此,在您的情况下,我们需要在分配后重置它:

Vehicle.prototype.constructor = Vehicle;
Car.prototype.constructor = Car;
Ferrari.prototype.constructor = Ferrari;

// or, we combine it with the Object.create call:
Car.prototype = Object.create(Vehicle.prototype, {
    constructor: {value:Car, configurable:true, writable:true}
});

请注意,无需在构造函数中显式设置它(如在第二个示例中),在对象上创建一个额外的属性。这就是继承的目的。

或者我们根本不关心“构造函数”属性:

我应该使用最后两种方法中的哪一种来让对象被识别为法拉利?还是我应该不在乎 - 毕竟“instanceof”仍然有效?

要将对象识别为法拉利,请始终使用instanceof。 “构造函数”属性不可靠。它可能被忘记设置,或者它不是您在比较中所期望的:像someObj.constructor === Car 这样的“类型检查”不适用于法拉利或任何其他正确设置其构造函数属性的“子类”。它与instanceof 一样。

【讨论】:

    【解决方案2】:

    为什么第一种方法能正确识别对象为法拉利?

    因为对象o的构造函数是函数Ferrari

    我在这两种方法中的继承是否正确?

    您在第一种方法中做得正确,但在第二种方法中做得不正确。

    如果我已经正确地完成了继承,我应该使用最后两种方法中的哪一种来让对象被识别为 法拉利?还是我应该不在乎 - 毕竟“instanceof”仍然有效?

    为了统一起见,您应该在函数体中设置每个原型的构造函数属性,这样您就不必这样做了:

    Car.prototype = Vehicle();
    Car.prototype.constructor = Vehicle;
    
    Aeroplane.prototype = Vehicle();
    Aeroplane.prototype.constructor = Vehicle;
    
    ...
    

    您应该使用:

    function Vehicle() {
        var vehicle = Object.create(Vehicle.prototype);
        vehicle.constructor = Vehicle;
        return vehicle;
    }
    
    Car.prototype = Vehicle();
    function Car() {
        var car = Object.create(Car.prototype);
        car.constructor = Car;
        return car;
    }
    
    Ferrari.prototype = Car();
    function Ferrari() {
        var ferrari = Object.create(Ferrari.prototype);
        ferrari.constructor = Ferrari;
        return ferrari;
    }
    
    var o = new Ferrari();
    console.log(o);
    

    这正确地导致了与您的第一个示例中的结构相似的结构。

    【讨论】:

    • 这对我来说没有多大意义。属性容易被继承,为什么还要在构造函数中设置呢?
    • @Bergi 您用作原型的对象的constructor 属性每次都需要在OP 发布的代码中重置。例如,如果我有 10 种车辆,每次我将新车的原型设置为Vehicle(),我还需要做Motorcycle.prototype.constructor = Vehicle;。我这样做的方式 constructor 属性已经设置好了。
    • 不,Motorcycle.prototype.constructor 应该是 Motorcycle。我想问的是为什么你在做car.constructor = Car(对于每个实例)而不是Car.prototype.constructor = Car(一次)。
    • @Bergi 不,Motorcycle.prototype.constructor 应该是 Vehicle。对象原型的constructor 属性应该指向创建该原型的函数。引用MDN(关于constructor):Returns a reference to the Object function that created the instance's prototype.CarVehicle的一个实例,所以它的构造函数是Vehicle
    • Car.prototype.constructor = Car 会做与car.constructor = Car 完全不同的事情。将实例的构造函数设置为自身是不正确的。 Car 是由Vehicle 构造的,而不是自己构造的。