9
// Case A
function Constructor() {
  this.foo = function() {
    ...
  };
  ...
}

// vs 
// Case B
function Constructor() {
  ...
};

Constructor.prototype.foo = function() {
  ...
}

人们建议使用原型的主要原因之一.foo是在原型的情况下创建一次,而this.foo在使用其他方法时创建多次。

然而,人们期望口译员可以优化这一点。foo因此在案例 A中只有一个函数副本。

当然,由于闭包,您仍然会为每个对象拥有一个唯一的作用域上下文,但与每个对象的新函数相比,这具有更少的开销。

现代 JS 解释器是否优化了案例 A,所以只有一个函数副本foo

4

3 回答 3

10

是的,创建函数会占用更多内存。

...而且,不,解释器不会将案例 A 优化为单个函数。

原因是JS 作用域链要求函数的每个实例在创建时捕获可用的变量。也就是说,现代解释器对案例 A 的理解比过去更好,但主要是因为闭包函数的性能在几年前是一个已知问题。

由于这个原因, Mozilla 表示要避免不必要的闭包,但闭包是 JS 开发人员工具包中最强大且最常用的工具之一。

更新: 刚刚运行了这个测试,使用 node.js(它是 V8,Chrome 中的 JS 解释器)创建了 1M 的构造函数“实例”。随着caseA = true我得到这个内存使用情况:

{
    rss: 212291584,       //212 MB
    vsize: 3279040512,    //3279 MB
    heapTotal: 203424416, //203 MB
    heapUsed: 180715856   //180 MB
}

随着caseA = false我得到这个内存使用情况:

{
    rss: 73535488,       //73 MB
    vsize: 3149352960,   //3149 MB
    heapTotal: 74908960, //74 MB
    heapUsed: 56308008   //56 MB
}

所以闭包函数肯定会消耗更多的内存,几乎是 3 倍。但从绝对意义上讲,我们只是在谈论每个实例约 140-150 字节的差异。(但是,这可能会增加,具体取决于创建函数时您拥有的范围内变量的数量)。

于 2011-09-16T23:26:46.927 回答
2

我相信,在节点中进行了一些简短的测试之后,在案例 A 和 B 中foo,内存中的函数的实际代码只有一份副本。

Constructor()案例 A - 为存储对函数代码的引用及其当前执行范围的每次执行创建一个函数对象。

案例 B - 只有一个作用域,一个函数对象,通过原型共享。

于 2011-09-16T23:40:35.653 回答
0

javascript 解释器也没有优化原型对象。它只是每种类型只有一个(多个实例引用)的情况。另一方面,构造函数创建新实例和其中定义的方法。所以根据定义,这真的不是解释器“优化”的问题,而是简单地理解正在发生的事情。

附带说明一下,如果解释器要尝试合并实例方法,那么如果您决定更改特定实例中 one 的值,则会遇到问题(我希望不要将头痛添加到语言中):)

于 2011-09-16T23:37:04.190 回答