【问题标题】:Does prototype really matter in JavaScript if the class will never be subclassed?如果类永远不会被子类化,原型在 JavaScript 中真的很重要吗?
【发布时间】:2011-10-21 21:22:19
【问题描述】:

所以,在回答这个网站上的另一个问题时,我为某人编写了一个类,以便在 JavaScript 中创建一个 BitArray 实例。我发布的代码如下所示:

var foo = function(param1) {
    this._member = param1;
    this.getMember = function() { return this._member; };
    this.setMember = function(val) { this._member = val; };
    this.alertMember = function() { alert(this._member); };
    foo.STATIC_CONSTANT = "some value";
}

我收到的其中一个 cmets 是相当严厉的“你绝对应该将方法添加到 foo.prototype 而不是这个”。如果我接受这种批评并将其应用于上述内容,它会将代码变成:

var foo = function(param1) {
    this._member = param1;
}
foo.prototype.getMember = function(){ return this._member; };
foo.prototype.setMember = function(val) { this._member = val; };
foo.prototype.alertMember = function() { alert(this._member); };
foo.STATIC_CONSTANT = "some value";

我的问题是,假设 foo 永远不会被子类化,这两种方法之间有什么区别?

需要明确的是,我并不是在提倡编写草率的代码 - 我认为评论者发现我草率了 - 但是当我承认他的正确性时,这确实让我思考 - 如果我是创建一个有效的密封类?

我在这里缺少一些性能或功能后果吗?或者当一个类不会被子类化时使用原型无关?

【问题讨论】:

  • 只是为了澄清:我没有说“方法永远不应该在原型之外”,这绝对不是这种情况。有很多有效的用例,您希望在对象实例上而不是在对象的原型上拥有方法。 这个不是其中之一。
  • @Daniel Baulig - 诽谤已更正:)

标签: javascript


【解决方案1】:

有多种原因。首先,虽然我猜在 JavaScript 中谈论类是有误导性的。 JavaScript 中没有类,只有原型。

让我们分析一下在您的第一种方法中实际发生的情况以及在第二种方法中发生的情况。当第一种方法的代码运行时,它定义了一个函数,当它作为构造函数调用时,将创建一堆其他函数并将它们分配给新创建的对象的成员。您应该知道,每次调用该函数时都会执行此操作。因此,如果您调用该函数一次,您将拥有一个对象,总共有 4 个函数。如果两次调用该函数,就会有两个对象,总共有 8 个函数,以此类推。每次,您使用此构造函数创建一个新对象,您将为它创建一组完整的函数。这些功能中的每一个都需要内存。此外,试图在代码中发现热点的现代 JIT 编译器可能无法将这些函数识别为热点,因为不是让一个函数被多次调用,而是有几个函数,每个函数只被调用一次。此外,即使 JIT 能够将这些函数识别为热点,它也必须单独编译每个函数,这将需要额外的工作。

话虽如此,我相信“foo 永远不会被子类化”的假设不应该是有效的。您不能假设其他人不想从您的对象继承或想要扩充您的对象的原型。目前这是不可能的。想象一下,我想更改 foo.STATIC_CONSTANT。我不能,因为每次我构造一个新的 foo 时,它都会再次覆盖我更改的 foo.STATIC_CONSTANT。对于我可能想做的任何其他增强也是如此。

所有这些问题都不会出现在第二种正确的面向对象的javascript模式中。

【讨论】:

  • +1 以获得详尽的答案。关于子类化,在这个问题的范围内,我问的是,在这个假设有效的情况下,忽略原型的后果是什么?尽管就每个实例的性能命中和优化未命中而言,您的回答非常充分。
  • +1,很好地解释了第一个示例中发生的情况。
【解决方案2】:

是的,有区别。

在第一个示例中,每次创建类的新实例时,都会重新定义其方法。这使得它在速度和内存方面的效率大大降低。

举例说明:

function C1 () {
    this.foo = function () { };
}

function C2 () {}
C2.prototype.foo = function () {};

c1a = new C1();
c1b = new C1();

c2a = new C2();
c2b = new C2();

c1a.foo === c1b.foo;  // false
c2a.foo === c2b.foo;  // true

【讨论】:

  • 实际上,它们没有重新定义(也不是类,但这是另一个问题)。这些方法是从头开始定义的。
  • 是的,但是该术语确实会与 Javascript OOP 混淆。
  • 好的,我们可以省略类,但是方法没有重新定义。当构造函数正在执行时,它们在this 对象上根本没有定义,如果它们是在原型上定义的,它们将可以被它访问。
  • @whitequark:我不确定我是否在关注你。是的,它们不是重新定义,你只是得到n 不同但等效的函数,每次你从“类”实例化一个新对象时一个。在“坏”示例中,它们被分别添加到类的每个单独实例中。在“好”的方式中,函数被创建一次并添加到原型中。因为它们存在于新创建对象的原型上,所以可以像您期望的那样访问它们。此外,在构造函数内部,它们仍然可以访问,但只有在为该实例定义它们之后。
  • 嗯嗯。可能我们在这里讨论的不是 JavaScript 语言问题,而是(我的)英语语言问题。没关系。
【解决方案3】:

在这里,您将从头开始为数组的每个实例创建三个函数。这有一些内存成本;可以讨论这个成本是否很大,但是如果您尝试创建该数组的数千个实例,您会注意到差异。

对象创建也会慢一些,但在最近优化的运行时(如 V8)上不会慢很多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-14
    • 1970-01-01
    • 2012-04-12
    • 1970-01-01
    • 2010-09-12
    • 2010-09-13
    • 1970-01-01
    • 2016-10-17
    相关资源
    最近更新 更多