Closure Compiler 高级模式及更多思考

前言

Google Closure Compiler 是 Google Closure Tools 的一员,在 2009 年底被 Google
释出,早先,有 玉伯 的 Closure Compiler vs. YUICompressor,主要就 压缩率
上进行了对比,另外有 承玉 的 应用 closure compiler 高级模式,对 CC
的高级模式做了些介绍

本文将详细介绍 CC 的高级模式部分,更重要的是,阐述 CC 高级模式背后的思考

CC 是真正的编译器

Closure Compiler 和 YUICompressor 并不是同类产品,虽然 CC 和 YC 同样产出压缩后的 JS 文件,
但是 YC 只做了词法上的扫描,而 CC 并不只是一个 compressor 那么简单,器如其名,它是一个
compiler

对于一个 compiler,一般地,它需要做到

  1. 检查源文本中语法、语义、语用上的错误
  2. 根据分析产出物(符号表、语法树等)产出目标 / 中间代码
  3. 优化

代码错误一般来自三个方面:

  1. 语法(Syntax)

    表示构成语言句子的各个记号之间的组合规律

    大体上,parser / interpreter 在词法分析和语法分析阶段,产生符号表、语法树等分析产出物,
    具体见编译原理教科书……

    语法上的错误,如:

    doSomething(;) // SyntaxError: Unexpected token ;
    

    根据语法规则,在非 for 语句中的 ; 意义是分隔符,而分隔符前的 ( 并没有配对 )
    因此报错

  2. 语义(Semantics)

    表示各个记号的特定含义(各个记号和记号所表示的对象之间的关系)

    compiler 需要根据语义分析产出中间代码,对于不产生中间代码的语言如 JS,则在运行时的
    解释期间指出错误

    语义上的错误,如:

    0 = {}; // ReferenceError: Invalid left-hand side in assignment
    

    根据赋值运算符 = 的意义,左操作数不能为字面量,所以虽然这个赋值语句包含了必需的
    左操作数、运算符、右操作数,仍然出错

  3. 语用(Pragmatics)

    表示在各个记号所出现的行为中,它们的来源、使用和影响

    语用上的错误,如:

    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 的高级模式前,还是简明介绍一下功能体系

编译级别

CC 的 compilation_level 包括三个级别:

  1. WHITESPACE_ONLY

    只删除空白、注释

  2. SIMPLE_OPTIMIZATIONS

    在 WHITESPACE_ONLY 基础上将局部变量和参数转成短名称

  3. ADVANCED_OPTIMIZATIONS

    更加激进的重命名、移除垃圾代码、内联函数

可以看到,SIMPLE_OPTIMIZATIONS 级别的 CC,和 YC 无异,没做什么真正的编译工作,所以说,
使用了高级模式的 CC 才是四肢健全的 CC =。=

约束条件

使用 CC 有一定约束条件,这影响到我们的编码风格:

  1. WHITESPACE_ONLY

    • 不认可 JS 1.5 以上版本的语言特性
    • 不保留注释
  2. SIMPLE_OPTIMIZATIONS
    • 完全禁用 witheval
    • 字符串中引用的函数名 / 参数名不会改动(CC 不改动所有字符串)
  3. ADVANCED_OPTIMIZATIONS 模式下的约束放到下文详述

注解

Annotations 也是 CC 的重要组成部分,使用 JSDoc 风格,用以辅助高级模式下的编译,下文详述

使用 CC 高级模式

在 CC 下,启用高级模式的方法是加入参数 --compilation_level ADVANCED_OPTIMIZATION

作为一个 compiler,CC 的高级模式下,额外的优化政策是

  1. 更激进的重命名,如 obj.property 改为 a.b,将深度过高的命名空间平坦化等
  2. 移除垃圾代码,如删除未被调用的方法定义,警告逻辑死角(return 后的语句等)
  3. 将函数内联,如 a call b, b call c,a(),那么直接执行 c()

要达到高级模式的预期优化效果,开发者必须对自己做一些约束,因为 js 是弱类型、动态性的。否则
js 的这种灵活将使 compiler 无能为力

总体上,这种约束包括限定某些 js 编码风格,以及使用相应的 JSDoc 注解

以下详述具体的约束以及代码的检查 / 优化效果:

强类型的模拟

  • @param 和 @type 中定义的类型会在编译期间得到检查,同样避免了在运行时检查,提高性能
  • @const 标记常量,当常量被写时会报错
  • 模拟枚举,将同类可枚举常量定义为一个对象字面量,使用 @enum 标记:
    var STATUS = {
        LOADING: 3,
        COMPLETE: 4
    };
    

    编译结果中 STATUS.LOADING 会被直接替换为 3,其实完全模拟了 C 等语言中的枚举

  • 使用 @constructor 标注函数为构造器,它仅能被实例化,而不可用作普通方法,
    甚至是工厂方法,CC 会确保构造器被合法使用,否则报错。这样确保开发者不必在 运行时
    判断,构造器函数到底以怎样的形式被调用
  • 在表达式中也可以使用 @type 来限定类型,这对于 JSON 特别有用,如
    var data = /** @type {UserModel} */({
        firstName : 'foo',
        lastName : 'bar'
    });
    

    在这里 UserModel 是个构造器,也可以使用 @typedef 来自定义复杂的数据类型

域可见性的模拟

  • 使用 @private 标注私有域,私有域被外部引用会报错。开发者也可以按照“国际惯例”给私有域加上
    _ 前缀或后缀,以提醒自己 / 协作者这是一个私有域,@private 注解用来告诉 CC;
    这样,开发者可以不必使用诸如老道的“模块模式”等技巧来真正地隐藏私有变量,将检查工作丢给
    CC,让开发尽可能朴实简单
  • 类似有 @protect

类系统的模拟

  • 使用 @extends 标注继承关系,继承体系会被优化
  • 使用 @interface 标注接口,接口是类似 function ThisIsAInterface(obj) {}
    的函数体为空的构造器定义,编译后将移除其相关代码。同时,标注 @implements 的构造器必须实现
    implemented 的接口的所有方法(正如其他 OO 语言一样),否则,CC 报错。这同样简化了接口 /
    实现的约束,靠 CC 来保证实现关系的可靠性

条件编译的模拟

  • 使用 @define 标记状态开关,适用于调试 logger 等 开发 / 发布 状态需要分离的模式。
    可以在编译时指定参数来标识 define 参数的状态。这其实就是一个条件编译,真给力……

对象平坦化及属性名缩减

  • 对象属性会被编译为单变量,比如 foo.bar to foo$bar,这种标记方法看起来很像 java
    中被编译出来的内部类~~之后 foo$bar 被进一步缩短。对象之所以能被平坦化是因为在
    js 中对象可以看做是一群引用 / 原始数据类型的容器
  • 但是,js 对象实际上更复杂,所以被平坦化后会带来一些副作用,比如如果在对象(字面量)中使用
    this 指针,则编译后的结果会导致 this 指向错误。所以 Google 建议仅在 constructor 和
    prototype methods 中使用 this,这意味着,在所谓类单例(对象字面量)和类的静态方法(绑定到
    constructor 上的函数)中都避免使用 this 指针
  • 在缩减对象属性 / 方法的名称长度时,有另外一个注意点,那就是必须始终使用 dot syntax(.
    运算符),而不使用 quoted string([] 运算符),除非索引名是一个变量。这是因为 CC
    始终不处理字符串中的内容,所以,var o = { longName: 0 }; o["longName"] 会被翻译为
    var a = { b: 0 }; a["longName"] 导致出错。实在想使用 quoted string,则在 定义的时候
    也要使用 quoted string
  • 对于全局变量,如果出现以 window.property 的形式引用的,必须始终定义为 window.porperty 形式:
    window.property = 1;
    var property = 1; // wrong!
    

    否则也会杯具,CC 可不会 window.property 翻译为 window.a

垃圾代码的移除

  • 一个函数声明却未被调用时,默认地,声明体将被干掉
  • 在这种机制下,如果一个方法是以 for in 的形式调用的,那么原方法也会被干掉,因为这种 动态特征
    使得 CC 无法清楚方法是否确实在 for in 的时候被调用了
  • 对于一些 unreachable 的代码,CC 将报警告
  • 如果要产出一份被调用的公共接口,例如库,使用称作 export 的方法将函数导出,防止函数定义被
    CC 回收。具体的做法是将函数绑定到某个容器,比如:

    function displayNoteTitle(note) {
        alert(note['myTitle']);
    }
    // Store the function in a global property referenced by a string:
    window['displayNoteTitle'] = displayNoteTitle;
    

    对于需要 export 的函数,均使用 quoted string 风格

背后的思考

根据以上高级模式优化的行为分析可知,CC 附加给开发者的约束主要有:

  1. 强制以强类型的静态语言风格编写 js,将关注点从 运行时的动态技巧 转移到 组织代码、编写逻辑
    本身。

    而可能由弱类型系统和动态特征产生的问题和风险则交给 CC,即通过开发者与 CC 达成一种
    编码约定 而规避掉

  2. 严格要求区分 面向开发者的代码面向机器的代码

    虽然不像 C 等语言会编译产生目标代码,但是 CC 在一定程度上也生成了 面向机器的 js
    包括压缩空白、缩减标识符、条件编译和冗余代码去除。

    这和第一点其实是一脉相承的,同样要求开发者 将关注点转移到开发本身

  3. 使用规范化的接口方式。

    这不仅包括要求开发者使用恰当的 annotation(extend, interface, …),同时也给整个 OO-JS
    打下了一个框架,开发者必须使用同样的模式进行 OO 编码

    另外,要求使用 export 技术统一导出公共接口更强化了这一点

    总之,这一点进一步限定了开发者的编码风格,但是带来的好处是明显的:可读、可控、一致性

曾经有读过 Closure Library 源码的同学评论道:

Google 根本不懂怎么写 javascript!代码里面各种冗余,并且充满了 java 的味道!

当时确实也有这种感觉,比如 Google 把 if(foo) 写作 if(foo != undefined) 等等

Javascript 固然充满了丰富的动态特征,而且很多特性非常优雅,能够让代码简洁精悍,或者构造出一些
令人惊叹 的技巧,但是也会产生一些副作用:

  • 首要的问题是可读性,静态的东西容易一目了然,动态的东西需要经过一番运算才能得出结论。
    比如 js 中的 极晚绑定,再比如 标识符运行时重写
  • 其次的问题是执行性能。一个比较经典的众 js 工程师都在使用的技巧就是“模拟函数重载”——
    在函数体内判断 arguments 的特征,从而对应给出不同的逻辑。由于缺乏强类型,js 本身不能具备
    真正的重载,但是运行时的判断在带来灵活性的同时,必然会多出很多模拟重载的逻辑,降低性能

在今年的 D2 大会上,Hedger 同学指出,大多数 js 开发者像是个 ninja(忍者),他们身怀绝技、
神鬼莫测,单兵作战还可以,但是一旦碰到 army(军队,比如 Google 团队这样的 =,=)就是个悲剧

我比较欣赏这个比喻,大团队要良好地协作,必需遵循一定的规范和限制,优先保证可读性和一致性,与此同时
失去的是奇技淫巧、自由灵活。所以采用何种编程风格、理念,需要具体问题具体分析…………

至少,目前 CC 提供了一个好的思路,它的高级模式推崇的编程风格也是很值得尝试、借鉴的

最后附上 CC 的常用命令选项……选项实在是有够多……

CC 常用命令选项

  • –charset VAL 对所有文件定义的编码格式
  • –compilationlevel [WHITESPACEONLY SIMPLEOPTIMIZATIONS ADVANCEDOPTIMIZATIONS]
    设定编译级别
  • –debug 开启 debug 选项
  • –define (–D, -D) VAL 设定文件中使用 @define 标注的开关值,即条件编译
  • –externs VAL 编译代码需要调用未编译的代码时,使用它
  • –formatting [PRETTYPRINT PRINTINPUT_DELIMITER] 格式化输出
  • –js VAL 输入文件,多指定多个,将会被合并
  • –jsoutputfile VAL 输出文件,如果不指定的话,直接输出到 standard output 流
  • –module VAL 定义模块
  • –output_manifest VAL 打印编译文件清单
  • –print_tree 打印语法分析树
  • –warning_level [QUIET DEFAULT VERBOSE] 设定报错模式

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索编译
, 函数
, 代码
, 开发者
, 模式
, 构造器
, cc语言
, cc++编程语言
, cc++编译
, cc语言c程序
, js高级技巧
, js函数高级技巧
, js高级函数
js调用java接口
closure compiler、closure compiler下载、js closure compiler、closurecompiler、思考模式,以便于您获取更多的相关知识。

时间: 2024-11-01 22:37:46

Closure Compiler 高级模式及更多思考的相关文章

Closure Compiler vs. YUICompressor

Google Closure Compiler 挺让人心动,昨晚仔细试用了一把,与 YUICompressor 的对比如下: 下载 DOS 脚本包:downloads list

discuz! 7.2 发帖和回复直接采用高级模式技巧

最近总有朋友问我同一个问题:如何才能让http://www.aliyun.com/zixun/aggregation/11656.html">discuz 7.2 发帖和回复的时候直接采用高级模式,而不是默认是那个弹窗模式,如下图所示: 如果站长采用默认安装方法的话,安装完成后用户发帖和回帖都是先显示这个窗口,需要点击那个"高级模式",然后才能到那个完整编辑器的界面,如下图所示: 那么,问题已经描述出来了,该如何解决呢?其实非常简单,只需要设置一下就搞定,完整步骤如下:

ReactiveCocoa代码实践之-更多思考_Android

相关阅读: ReactiveCocoa代码实践之-UI组件的RAC信号操作 ReactiveCocoa代码实践之-RAC网络请求重构 1. RACObserve()宏形参写法的区别 之前写代码考虑过 RACObserve(self.timeLabel , text) 和 RACObserve(self , timeLabel.text) 的区别. 因为这两种方法都是观察self.timeLabel.text的属性,并且都能实现功能.估计是作者原本用的其中一种后来对另一种也提供了支持,究竟有什么区

ReactiveCocoa代码实践之-更多思考

相关阅读: ReactiveCocoa代码实践之-UI组件的RAC信号操作 ReactiveCocoa代码实践之-RAC网络请求重构 1. RACObserve()宏形参写法的区别 之前写代码考虑过 RACObserve(self.timeLabel , text) 和 RACObserve(self , timeLabel.text) 的区别. 因为这两种方法都是观察self.timeLabel.text的属性,并且都能实现功能.估计是作者原本用的其中一种后来对另一种也提供了支持,究竟有什么区

中国互联网的进化:克隆、改良、创新以及更多思考

作者 卢刚 on August 12, 2009 · 文章分类 中国Web2.0, 评论 [卢刚: 本篇文章的英文原稿参见这里.最所以写它,重要的原因是非常希望国外的关注中国互联网的朋友们能够真正从多些角度理解中国的互联网产业.有很多问题,但是也有更多希望.再次感谢Yeeyan朋友的帮助翻译.] 从CNNIC最近公布的报告(第24次中国互联网发展报告)来看,中国互联网和手机市场潜力巨大.对中国互联网最常用的描述是什么?可能就是"一个庞大的市场"了: 3.16亿互联网用户,约290万个中

生意宝欲复制“中服模式” 培育更多“小网盛”

近日,在成功完成中国服装网"http://www.aliyun.com/zixun/aggregation/15941.html">重组计划",并高调启动"冲击创业板计划"之后,网盛生意宝方面又发布了一封公开信,信里表示:2010年后,将复制更多的"中服模式",从而培育出更多的"小网盛". 公开信中孙德良称,今后网盛生意宝将吸纳一批像中国服装网那样的优质行业网站,并通过引入生意宝上市公司的战略投资,利用资本力量

三星S5如何在儿童模式下载更多软件

提示: 由于下载软件需要使用流量费用,建议您连接WLAN无线网络后再进行下载. 1. 在待机模式下,点击右下方的[家长控制]图标.   2. 根据提示输入您设置的PIN码.   3. 点击左上角的[菜单]图标.   4. 选择[儿童商店].   5. 点击要下载的软件分类.(这里以"学习"为例)   6. 点击要下载的软件图标.(以"Caillou的拼图之家"为例)   7. 点击[确定].   8. 点击[免费下载].   9. 点击[登录].   提示: 若没有

SEO新手思维解读:更多思考seo

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 很多武汉seo都致力于seo技术和细节,却忽视了seo其真正的价值及存在的意义.首先我们从最根本的方面下手来研究研究. 搜索引擎在乎什么? Seo 即是搜索引擎优化,那有没有想过搜索引擎真正在乎的是什么?看似一个简单的问题,但不是每个人都能给出其精准的答案,用搜索引擎自己的话来说,百度的使命是"让人们更便捷地获取信息,找到所求&qu

麦客:以截然不同的思路做互联网时代的CRM

2013 年7 月,麦客(MikeCRM)团队在产品还未上线时就得到来自红杉.宽带资本.北极光创投和金沙江创投联合成立的云天使基金的天使轮融资.麦客是一款帮助企业解决联系人信息收集和管理的在线表单类产品,比较特别的是,这个团队从校园创业开始,与其他年轻创业者热衷于天马行空地开发C端产品相反,他们决定切入针对企业的B2B市场.而且这款工具类产品选择了一个更为Niche的领域,专攻企业所涉及到的外部联系人信息管理.目前麦客的 主要功能在Web端实现,并且很好地结合微信公众平台实现了微信中的信息提醒.