如何在JavaScript中创建实例的实例

7
我希望创建一组独特的汽车实例供不同的人拥有。这些汽车将具有类似的基本规格,但它们的某些属性和方法将会有所不同。
我的问题在于我无法确定应该如何操作。在JavaScript中,如何处理或创建实例的实例?
var Car = function(make, country) {
    this.make = make;
    this.country = country;
};

var Ferrari = new Car('Ferrari', 'Italy');

var fred = new Person() {};
var fred.cars['Ferrari'] = new Ferrari(1200, 300000);

这会因为明显的原因导致错误。我知道它不是一个构造函数(见下文)。
Uncaught TypeError: Ferrari is not a constructor

我需要的是这样的内容,每一个不同的法拉利实例都有不同的价格和里程数。
var Ferrari = function(currentPrice, miles) }
    this.currentPrice = currentPrice;
    this.miles = miles;
    // this is an instance of car, aka it needs the result of this:
    // new Car('Ferrari', 'Italy');

};

弗雷德的法拉利是法拉利的一个实例,而法拉利又是汽车的一个实例。问题在于我无法想出一种方法来构建一个构造函数。有没有办法做到这一点,或者我只是走错了路?
其他注意事项:
我知道我可以基本上将每种类型的汽车作为静态JSON对象,并创建该对象的实例并添加新的唯一值。然而,我希望能够将Car保留为构造函数,以便在需要时轻松创建更多。
显然我在面向对象编程或JavaScript方面缺少一些理解,但如果有人能指点我方向,那就太好了。

1
Ferrari 应该是一个子类,而不是一个实例;它应该 extends Car - jonrsharpe
对于像我一样感到困惑的人,MDN上有一个很好的页面,详细解释了如何在JS中创建对象层次结构与基于类的语言的区别:https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Details_of_the_Object_Model - jamcd
2个回答

5
你需要的是一个派生构造函数和相关原型,有时被称为子类。在老式的ES5中,它看起来像这样:
var Car = function(make, country) {
    this.make = make;
    this.country = country;
};
var Ferrari = function(currentPrice, miles) {
    Car.call(this, "Ferrari", "Italy");
    this.currentPrice = currentPrice;
    this.miles = miles;
};
Ferrari.prototype = Object.create(Car.prototype);
Ferrari.prototype.constructor = Ferrari;

如何运作:

  • Ferrari 是一个构造函数,当调用时,使用 this 指向新实例,并传递给 Car 所需的参数。 Car 设定了实例的属性,然后继续执行 Ferrari 的代码,将传入的参数(在上面的例子中)记为属性。

  • 我们确保通过 new Ferrari 分配给实例的对象(来自于 Ferrari.prototype)使用 Car.prototype 作为原型对象,这样如果你向 Car.prototype 添加内容,它们也会出现在 Ferrari 中。

  • 我们确保 Ferrari.prototype 上的标准 constructor 属性引用 Ferrari

在 ES2015 中更加简洁(可以通过转译工具如 Babel 来使用):

class Car {
    constructor(make, country) {
        this.make = make;
        this.country = country;
    }
}
class Ferrari extends Car {
    constructor(currentPrice, miles) {
        super("Ferrari", "Italy");
        this.currentPrice = currentPrice;
        this.miles = miles;
    }
}

@Jordão:谢谢!已修复。 - T.J. Crowder
ES2015的例子看起来很像我在PHP中见过的,但是在Javascript中找不到类似的功能。谢谢! - jamcd
1
值得一提的是,OP的类模型有问题 - pricemiles属性实际上应该属于基类。 - Alnitak
1
@jamcd:它相对较新,是在2015年6月的规范中引入的,这就是为什么如果你想在实际应用中使用它,你需要进行转译的原因(如果你只想在当前的Chrome或NodeJS上使用它,则不需要)。而且,它故意与其他语言中的类似结构(例如Java)相似。 - T.J. Crowder
@Alnitak:谢谢。请原谅我的无知,这是因为它们是多个不同汽车的共同属性吗?如果你只想为某些汽车指定一个价格而不是其他汽车呢? - jamcd
1
@jamcd 每辆车都有一个价格(即使你不知道它是多少)。 - Alnitak

-1
如果你想让 Car 实例成为构造函数,Car 必须返回一个构造函数。
问题在于这样会继承自 Function.prototype 而不是 Car.prototype。如果不希望这样,可以使用 Object.setProtetypeOf 来解决:
function Person() {
  this.cars = {};
}
function Car(make, country) {
  var instance = function(currentPrice, miles) {
    this.currentPrice = currentPrice;
    this.miles = miles;
  };
  instance.make = make;
  instance.country = country;
  Object.setPrototypeOf(instance, Car.prototype); // slow!!
  return instance;
}
var Ferrari = new Car('Ferrari', 'Italy');
var fred = new Person();
fred.cars.ferrari = new Ferrari(1200, 300000);

或者,您可以添加一个方法来“实例化”您的实例。

function Person() {
  this.cars = {};
}
function Car(make, country) {
  this.make = make;
  this.country = country;
}
Car.prototype.instantiate = function(currentPrice, miles) {
  var instance = Object.create(this);
  instance.currentPrice = currentPrice;
  instance.miles = miles;
  return instance;
};
var ferrari = new Car('Ferrari', 'Italy');
var fred = new Person();
fred.cars.ferrari = ferrari.instantiate(1200, 300000);

感谢提供另一种选择。如果您不介意我问一下,使用这种方法与“call”方法相比有什么好处吗? - jamcd
@jamcd 这是不同的方法。call 用于子类化,以便从扩展一个调用超级构造函数。但是您想要实例化实例,而不是子构造函数。因此,已经调用了超级构造函数来创建您想要实例化的实例,然后没有理由再次调用它。我提供的方法比子类化的优点是您不需要预先知道汽车名称(例如 Ferrari)。根据问题,我理解这就是您想要的,而不是子类化。 - Oriol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接