【问题标题】:Javascript Function `Recreation´?Javascript函数“娱乐”?
【发布时间】:2011-08-12 01:59:59
【问题描述】:

我阅读了很多 JavaScript 代码,看到了很多不同风格的所谓的类。我正在开发一个轻量级 DOMish 类,它包含我的模板脚本的最低限度,在 Node.JS 上运行(它使用 JSON 将 DOMish 实例转换为另一个 DOMish 实例,然后将其序列化为 HTML,缓存原始 DOMish 实例并根据模板请求克隆它)。

以上与我的问题无关。阅读http://www.phpied.com/3-ways-to-define-a-javascript-class/,第 1.2 节后。内部定义的方法

1.1 的一个缺点。就是每次创建新对象时都会重新创建getInfo()方法。

定义类的方式,如第 1.1 节所述,是(稍微修改以反映我自己的情况,使用本地/私有变量):

function Apple (type) {
    var that = this;

    // local-variable
    var color = 'red';

    // local-(extern)-function
    var infoProvider = function() { // inner-function
        // note the danger of this and that!
        return that.color + ' ' + that.type + ' ' + this.type;
    };

    this.__defineGetter__("type", function() { return type; });

    this.getInfo = function(otherContext) {
        return infoProvider.call(otherContext); // other "this" scope
    };
}

var apple = new Apple('iPod');
apple.getInfo({type: 'music player'}); // 'red iPod music player'

我碰巧使用了相同的样式,因为该函数现在可以访问在构造函数中定义的本地/私有变量和函数。但是“每次创建新对象时都会重新创建[函数]”这句话。吓到我了(性能明智)!当我使用使用 V8 的 Node 时,我一直认为函数只创建一次并以某种方式缓存,当在不同的对象上调用时,只使用不同的上下文(thises 和thats) .

我应该害怕“娱乐”吗?与基于原型的函数相比,它有多糟糕?还是这纯粹是审美(人们喜欢将东西放在一起,在构造函数中,VS,人们喜欢原型)?

【问题讨论】:

标签: javascript performance function scope


【解决方案1】:

虽然 V8 确实缓存了很多东西,这不是其中一种情况,如下代码所示

> function foo(){ this.bar = function(){ return this.x; }; this.x = Math.random(); }
> var ary = [];
> for(var i=0; i<1000000; i++){ ary.push(new foo()); }

以上使用 144MB 内存。

> function foo(){ this.x = Math.random(); }
> foo.prototype.bar = function(){ return this.x; }
> var ary = [];
> for(var i=0; i<1000000; i++){ ary.push(new foo()); }

这使用了 68MB 的内存。

注意 V8 源代码位于:

http://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/compilation-cache.h?r=5284

似乎暗示该函数的编译可能确实被缓存了:

编译缓存保持共享 编译脚本的函数信息 和评估。共享功能信息 使用源查找 字符串作为键。对于常规 表达式编译数据是 缓存。

但是测试表明即使编译编译了,新的对象还是会被创建,好像你不创建新的对象你会破坏JS :)

编辑

进一步测试:

function foo(){
    this.bar = function(){
        this.x = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
        return this.x;
    };
    this.x = Math.random();
}
var ary = [];
for(var i=0; i<1000000; i++){ ary.push(new foo()); }

使用 144MB 内存。由于我添加了一个 100 字符的字符串,如果函数本身没有被缓存,我们将使用额外的 100MB 左右的内存。

所以上面说明是的,函数本身是缓存的。但它仍然需要用一个新的对象来表示。

【讨论】:

  • 非常有趣!但是,不应该像 @lwburk 所说的那样共享一个函数,就像原型一样?只有当一个函数是唯一的时,才从原型中派生它?这不是一个巨大的优化吗,像我这样的程序员为什么要关心这种微内存管理问题?
  • 不,因为如果函数是共享的,而您后来写道:var x = new foo(); x.bar.something = 22;,则不应在 foo 的所有实例之间共享 .bar.something 属性,因为您将 bar 显式设置为单独的函数每一次。因此,需要一个新对象来表示该功能。正如我链接到的(在编辑中),实际函数可能会被缓存,但它仍然需要由一个新对象表示。
  • P.S. __defineSetter____defineGetter__ 方法是否相同?
  • 结论:只有对象消耗内存,而不是函数内容(即函数代码)。
  • 是的,差不多。通过每次重新声明该函数,您可以获得一个新对象,但您不会因为在其他 JS 引擎中重新声明它而受到惩罚。
【解决方案2】:

以下代码:

this.getInfo = function(otherContext) {
    return infoProvider.call(otherContext); // other "this" scope
};

...创建一个新函数并将对它的引用分配给新创建对象的getInfo 属性。每次调用 new Apple 时都会发生这种情况。每个新实例都有自己的副本。

但是,这段代码:

Apple.prototype.getInfo = function(otherContext) {};

...创建getInfo 函数的一个共享实例,分配给构造函数的原型。

这种差异并不是纯粹的审美。这是创建实例方法的一种根本不同的方式。函数就像任何其他对象一样(即您可以为其分配属性)。例如,您可以这样做:

this.getInfo.classVariable = "whatever" 

这将只在一个实例的getInfo 函数上创建一个新属性。如果函数被缓存,那么该分配将影响所有实例,这不是我们想要的。

【讨论】:

  • 是否存在函数局部变量?因为当这样的事情存在时创建一个新函数是有意义的。我相信 JavaScript 没有函数局部变量,因此,它可以安全地缓存这样的this.getInfo = functions,对吧?它是否缓存它们(特定于 V8)?
  • @Pindatjuh - 函数就像任何其他对象一样(即您可以为其分配属性)。例如,您可以这样做:this.getInfo.classVariable = "whatever"。这只会更新一个实例的getInfo 函数。如果函数被缓存,那么该分配将影响所有实例,这不是我们想要的。
  • 附带说明,这种行为(允许将属性分配给函数)是否只在使用 new 运算符时才被允许,就像类一样?
  • @Pindatjuh - 不。您可以将属性分配给任何对象。函数是一流的对象。
猜你喜欢
  • 1970-01-01
  • 2010-12-16
  • 2011-07-03
  • 2021-08-09
  • 2023-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-20
相关资源
最近更新 更多