2

我最近一直在查看 Google Closure Compiler。我下载了 .jar 文件并进行了试驾。到目前为止,我必须说我印象非常深刻。我当然可以看到它在最小化之外的用处。对 Google 团队的支持!

不过,我确实有一个小小的抱怨。在我看来,就优化而言,您只有两个选择。它是 SIMPLE_OPTIMIZATIONS 或 ADVANCED_OPTIMIZATIONS。前者虽然足够,但恕我直言非常简单。一方面,除非我遗漏了什么,否则所有属性名称都不会受到影响。它也不会删除无法访问的代码。另一方面,后一种选择简直太具有破坏性了。

现在,我对 JavaScript 还很陌生,所以我很可能遗漏了一些东西。如果我说一些愚蠢的话,请随时教育我。也就是说,我可以理解在 JavaScript 中重命名的问题。Google 团队建议使用方括号表示法 (object['property']) 而不是点表示法 (object.property) 来访问您不想更改的属性,并且不要混合使用这两种用法。他们还建议使用以下模式“导出”方法:

MyClass = function(name) {
  this.myName = name;
};

MyClass.prototype.myMethod = function() {
  alert(this.myName);
};

window['MyClass'] = MyClass; // <-- Constructor
MyClass.prototype['myMethod'] = MyClass.prototype.myMethod;

但是,在某些情况下,您希望混合使用这两种表示法。假设我们正在构建一个游戏。游戏的代码完全隔离在一个闭包中。它不会将任何内容“导出”到全局范围,也不需要这样做。事实上,它真的不应该接触窗口对象。但是,它确实需要从 XML 配置文件中读取一些游戏内属性。

示例 JavaScript:

var TheGreatAdventure = (function(window) {

    function Fighter() {
        // Private to application
        this.id        = 42;
        // Accessible to XML configuration system
        this.name      = 'Generic Jen';
        this.hitPoints = 100;
        this.onAttack  = genericFighterAttack;
        this.onSpeak   = genericFighterSpeak;
        ...
    }
    Fighter.publishedProperties = ['name', 'hitPoints', 'onAttack', 'onSpeak']

    function genericFighterAttack() {...}
    function genericFighterSpeak() {...}

    function cassieAttack() {...}
    function cassieSpeak() {...}

    ...

    EntityReader = {
        ...
        function readFromXMLNode(attributes, entityClass, entityInstance) {
            for (var i = 0; i < attributes.length; i++) {
                var attribute = attributes[i];
                if (attribute.nodeName in entityClass.publishedProperties)
                    entityInstance[attribute.nodeName] = bindContext[attribute.value];
            }
        }
        ...
    }

}(window));

示例 XML 配置文件:

<Fighter name='Custom Cassie' onAttack='cassieAttack' onSpeak='cassieSpeak'/>

不仅上述系统无法分配属性,cassieAttack 和 cassieSpeak 函数将在最小化期间​​作为死代码被消除!

现在,我无法在整个游戏代码中使用括号表示法访问所有“已发布”属性。即使这样做没有运行时惩罚(不应该有),仍然涉及很多额外的输入,并且(IMO)是一个令人眼花缭乱的东西。有了这样的通用属性,所有内容都会在文本编辑器中显示为字符串,违背了语法高亮的目的!

在我看来,对这些属性的简单@preserve(或类似的)指令将允许在最终程序大小中以最低成本使用 ADVANCED_OPTIMIZATIONS。我错过了什么吗?

4

2 回答 2

3

编译器对此有支持,但它很笨拙,并且依赖于闭包库中名为“goog.reflect.object”的“原语”:

/** @nocollapse */
Fighter.publishedProperties = goog.object.transpose(goog.reflect.object(
    Fighter, {fullName:1, hitPoints:2}));

这避免了使用引用的属性。如果这是你从闭包库中使用的唯一东西,那么除了“goog.object.transpose”函数之外的所有东西都将被编译出来(goog.object.transpose 并不特殊,所以你可以自由使用替代实现)。这是类型安全的,可以与编译器的基于类型的优化一起使用(请参阅此处的描述:http ://code.google.com/p/closure-compiler/wiki/ExperimentalTypeBasedPropertyRenaming )。

关键是 goog.reflect.object 必须直接使用,将构造函数和对象字面量与您需要保留的属性的键一起使用。

您需要注意的另一件事是,在 ADVANCED 模式下,如果 Fighter.publishedProperties 在全局范围内定义,由于命名空间崩溃,它将从构造函数中删除。 @nocollapse防止这种情况。另一种方法是使用辅助方法来添加属性:

function addPublishedProperties(obj, value) {
  obj.publishedProperties = goog.object.transpose(value);
}

好的,我在这里介绍了很多内容,所以请务必让我知道您是否需要澄清。

于 2012-05-03T19:02:51.127 回答
3

这个答案被完全重写,原来有一种方法可以做 user1127813 想要的。

您需要提供一个属性映射文件,使用该--property_map_input_file标志将一些名称映射到它们自身。假设您有以下原始代码test.js

/** @constructor */
function Fighter() {
    this.ID        = 42;
    this.fullName  = 'Generic Jen';
    this.hitPoints = 100;
}
Fighter.publishedProperties = ['fullName', 'hitPoints'];

var jen = new Fighter();
var bob = new Fighter();

bob.ID = 54;
bob.fullName = 'Bob the Destructor';
bob.hitPoints = 1337;

for(i = 0; i < Fighter.publishedProperties.length; i++) {
    prop = Fighter.publishedProperties[i];
    alert(prop + ' = ' + bob[prop]);
}

像这样编译它:

java -jar closure-compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js test.js --property_map_output_file testprop.txt --js_output_file test2.js

您将获得一个新文件test2.js(内容无效)和另一个文件testprop.txt,其中包含:

ID:a
hitPoints:c
fullName:b

改变testprop.txt它看起来像这样:

ID:ID
hitPoints:hitPoints
fullName:fullName

然后使用testprop.txt作为输入而不是输出重新编译:

java -jar closure-compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js test.js --property_map_input_file testprop.txt --js_output_file test2.js

观察内容test2.js

var a=["fullName","hitPoints"],b=new function(){};b.ID=54;b.fullName="Bob the Destructor";b.hitPoints=1337;for(i=0;i<a.length;i++)prop=a[i],alert(prop+" = "+b[prop]);

现在使用点符号使用其原始名称访问所需的属性,并且程序将正确显示带有 bob 已发布属性的弹出窗口。

于 2012-05-03T14:46:51.420 回答