【转】Closure Compiler 高级模式及更多思考
Google Closure Compiler 是 Google Closure Tools 的一员,在 2009 年底被 Google
释出,早先,有 玉伯 的 Closure Compiler vs. YUICompressor,主要就 压缩率
上进行了对比,另外有 承玉 的 应用 closure compiler 高级模式,对 CC
的高级模式做了些介绍
本文将详细介绍 CC 的高级模式部分,更重要的是,阐述 CC 高级模式背后的思考
Closure Compiler 和 YUICompressor 并不是同类产品,虽然 CC 和 YC 同样产出压缩后的 JS 文件,
但是 YC 只做了词法上的扫描,而 CC 并不只是一个 compressor 那么简单,器如其名,它是一个
compiler
对于一个 compiler,一般地,它需要做到
代码错误一般来自三个方面:
表示构成语言句子的各个记号之间的组合规律
大体上,parser / interpreter 在词法分析和语法分析阶段,产生符号表、语法树等分析产出物,
具体见编译原理教科书……
语法上的错误,如:
doSomething(;) // SyntaxError: Unexpected token ;
根据语法规则,在非 for
语句中的 ;
意义是分隔符,而分隔符前的 (
并没有配对)
,
因此报错
表示各个记号的特定含义(各个记号和记号所表示的对象之间的关系)
compiler 需要根据语义分析产出中间代码,对于不产生中间代码的语言如 JS,则在运行时的
解释期间指出错误
语义上的错误,如:
0 = {}; // ReferenceError: Invalid left-hand side in assignment
根据赋值运算符 =
的意义,左操作数不能为字面量,所以虽然这个赋值语句包含了必需的
左操作数、运算符、右操作数,仍然出错
表示在各个记号所出现的行为中,它们的来源、使用和影响
语用上的错误,如:
doSomething(); // ReferenceError: doSomething is not defined
在这里直接调用了一个未定义的函数,导致出错
在一些其他场景中,虽然程序运行正确无误,但是仍然可以优化(这种优化并不是技巧上的),比如:
function doSomethingElse() {}
(function() {
return;
doSomethingElse(); // No Exception but Redundant: Unreachable code
})();
在这里,doSomethingElse 函数之前由于有 return,因此这个函数调用将永远不能执行,这种冗余代码
对整个程序来说毫无用处,可以去掉
对于 Closure Compiler 来说,它处理的对象是 js,不需要产生其他中间代码或汇编代码 / 机器码,
因此输出的还是 js,但是是经过分析的、优化后的 js;另外,它也可以选择输出 parse tree(使用
–print_tree 参数),所以,CC 的确完成了一个编译器需要实现的功能
在详细讨论 CC 的高级模式前,还是简明介绍一下功能体系
CC 的 compilation_level 包括三个级别:
只删除空白、注释
在 WHITESPACE_ONLY 基础上将局部变量和参数转成短名称
更加激进的重命名、移除垃圾代码、内联函数
可以看到,SIMPLE_OPTIMIZATIONS 级别的 CC,和 YC 无异,没做什么真正的编译工作,所以说,
使用了高级模式的 CC 才是四肢健全的 CC =。=
使用 CC 有一定约束条件,这影响到我们的编码风格:
with
和 eval
Annotations 也是 CC 的重要组成部分,使用 JSDoc 风格,用以辅助高级模式下的编译,下文详述
在 CC 下,启用高级模式的方法是加入参数 --compilation_level ADVANCED_OPTIMIZATION
作为一个 compiler,CC 的高级模式下,额外的优化政策是
要达到高级模式的预期优化效果,开发者必须对自己做一些约束,因为 js 是弱类型、动态性的。否则
js 的这种灵活将使 compiler 无能为力
总体上,这种约束包括限定某些 js 编码风格,以及使用相应的 JSDoc 注解
以下详述具体的约束以及代码的检查 / 优化效果:
var STATUS = {
LOADING: 3,
COMPLETE: 4
};
编译结果中 STATUS.LOADING
会被直接替换为 3,其实完全模拟了 C 等语言中的枚举
var data = /** @type {UserModel} */({
firstName : 'foo',
lastName : 'bar'
});
在这里 UserModel 是个构造器,也可以使用 @typedef 来自定义复杂的数据类型
_
前缀或后缀,以提醒自己 / 协作者这是一个私有域,@private 注解用来告诉 CC;function ThisIsAInterface(obj) {}
foo.bar to foo$bar
,这种标记方法看起来很像 javafoo$bar
被进一步缩短。对象之所以能被平坦化是因为在.
[]
运算符),除非索引名是一个变量。这是因为 CCvar o = { longName: 0 }; o["longName"]
会被翻译为var a = { b: 0 }; a["longName"]
导致出错。实在想使用 quoted string,则在 定义的时候window.property = 1;
var property = 1; // wrong!
否则也会杯具,CC 可不会 window.property 翻译为 window.a
function displayNoteTitle(note) {
alert(note['myTitle']);
}
// Store the function in a global property referenced by a string:
window['displayNoteTitle'] = displayNoteTitle;
对于需要 export 的函数,均使用 quoted string 风格
根据以上高级模式优化的行为分析可知,CC 附加给开发者的约束主要有:
而可能由弱类型系统和动态特征产生的问题和风险则交给 CC,即通过开发者与 CC 达成一种
编码约定 而规避掉
虽然不像 C 等语言会编译产生目标代码,但是 CC 在一定程度上也生成了 面向机器的 js,
包括压缩空白、缩减标识符、条件编译和冗余代码去除。
这和第一点其实是一脉相承的,同样要求开发者 将关注点转移到开发本身
这不仅包括要求开发者使用恰当的 annotation(extend, interface, …),同时也给整个 OO-JS
打下了一个框架,开发者必须使用同样的模式进行 OO 编码
另外,要求使用 export 技术统一导出公共接口更强化了这一点
总之,这一点进一步限定了开发者的编码风格,但是带来的好处是明显的:可读、可控、一致性
曾经有读过 Closure Library 源码的同学评论道:
Google 根本不懂怎么写 javascript!代码里面各种冗余,并且充满了 java 的味道!
当时确实也有这种感觉,比如 Google 把 if(foo)
写作 if(foo != undefined)
等等
Javascript 固然充满了丰富的动态特征,而且很多特性非常优雅,能够让代码简洁精悍,或者构造出一些
令人惊叹 的技巧,但是也会产生一些副作用:
在今年的 D2 大会上,Hedger 同学指出,大多数 js 开发者像是个 ninja(忍者),他们身怀绝技、
神鬼莫测,单兵作战还可以,但是一旦碰到 army(军队,比如 Google 团队这样的 =,=)就是个悲剧
我比较欣赏这个比喻,大团队要良好地协作,必需遵循一定的规范和限制,优先保证可读性和一致性,与此同时
失去的是奇技淫巧、自由灵活。所以采用何种编程风格、理念,需要具体问题具体分析…………
至少,目前 CC 提供了一个好的思路,它的高级模式推崇的编程风格也是很值得尝试、借鉴的
最后附上 CC 的常用命令选项……选项实在是有够多……
原文链接:http://ued.taobao.com/blog/2010/12/23/advanced-optimization-in-closure-compiler-and-more/