Google Closure Compiler的ADVANCED_OPTIMIZATIONS选项

时间:2012-05-03 14:42:08

标签: javascript google-closure-compiler

我最近一直在查看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。我错过了什么吗?

2 个答案:

答案 0 :(得分: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属性的弹出窗口。

答案 1 :(得分:3)

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

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

这避免使用带引号的属性。如果这是您从Closure库中使用的唯一内容,那么除了“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);
}

好的,我已经介绍了很多内容,所以如果您想澄清,请务必告诉我。