Hello World
Spiga

Jscex项目现状:UglifyJS解析器及AOT编译器

2011-04-15 02:04 by 老赵, 2703 visits

Jscex项目是我为了简化JavaScript异步的一个类库,支持任意JavaScript(ECMASCript 3)引擎。Jscex小巧而强大,可以极大地改善前端的AJAX及动画等场景的编程体验,同样也可以用在node.js进行服务器开发。从产生Jscex的想法到现在也有几个月的时间了,也一直想设法进行推广。在思考过程也发现了它在实际生产中可能会遇到的问题,于是前两个星期的主要工作,便是针对这些问题进行优化。首先我将Jscex的JavaScript分析器从Narcissus换成了UglifyJS,并基于node.js开发了一个简单的AOT编译器。接下来我也打算写个稍微详细一点的介绍,然后在国外社区看看反响如何。

Jscex的本质是一个用JavaScript编写的JavaScript编译器,因此我需要一个JavaScript实现的JavaScript解析器。我起初选择了著名的Narcissus项目,但由于它用到了SpiderMonkey的一些扩展,最终我使用的其实是NarrativeJS中旧版的Narcissus代码。我一直在设法减小Jscex核心的体积及执行速度(毕竟一个重要的场景是浏览器端),再加上不是很喜欢旧版Narcissus代码的解析结果,于是我也在不断寻找它的替代品。前段时间我发现了UglifyJS这个JavaScript压缩器,它的解析器移植于parse-js项目,后者是一个用Common Lisp实现的类库,因此输出结构也十分简单,一个“表”而已,执行速度也大大领先于Narcissus,体积也更小。于是我花了一个周末的时间将Jscex编译器改写为基于UglifyJS的实现。

在改写过程中,我也同样考虑了目标代码在压缩后的体积。我使用Closure Compiler的“高级”模式压缩代码,一般来说Closure Compiler的高级模式很破坏代码,我使用了各种方式来保证压缩后的代码能够正确执行。目前,如果您要在项目中使用Jscex编写异步程序,需要依次加载以下三个文件(它们都在项目源码的bin目录中):

  • uglifyjs-parser.min.js:UglifyJS解析器,大小20K,gzip后8K。
  • jscex.min.js:Jscex核心编译器,大小5.5K,gzip后1.8K。
  • jscex.async.min.js:Jscex异步核心类库,大小2K,gzip后0.9K。

如果您觉得gzip后10K左右的体积还是有些大,那么也可以使用目前已经提供的AOT编译器——虽然AOT编译器的原始目的并不是为了减小体积。

Jscex改善异步编程的原理,在于让程序员直接编写代码,使用普通的编程思路来实现算法,包括是用try...catch来捕获异常等等,而不会因为异步所需要的回调将代码拆得支离破碎。例如我们要实现冒泡排序算法的动画演示,也只需要使用传统编码方式实现算法即可:

// 标准算法
var bubbleSort = function (array) {
    for (var x = 0; x < array.length; x++) {
        for (var y = 0; y < array.length - x; y++) {
            if (array[y] > array[y + 1]) {
                swap(array, y, y + 1);
            }
        }
    }
}

// 演示动画
var bubbleSortAsync = eval(Jscex.compile("async", function (array) {
    for (var x = 0; x < array.length; x++) {
        for (var y = 0; y < array.length - x; y++) {
            var r = $await(compareAsync(array[y], array[y + 1]));
            if (r > 0) {
                $await(swapAsync(array, y, y + 1));
            }
        }
    }
}));

Jscex.compile会解析代码,并生成异步代码,并交给eval来解释执行。bubbleSortAsync和其中调用的compareAsync(比较两个元素大小,并暂停10毫秒)和swapAsync(交换两个元素,绘图,并暂停20毫秒)都是异步方法。但是无论在编写和使用上,异步方法和同步算法几乎没有区别——唯一的区别便是$await语句必须单起一行。这个限制一是为了保证开发人员可以明确分清普通的JavaScript代码及异步方法调用,二便是为了简化编译器的实现。例如,“理想情况”下类似以下的代码也需要支持:

f(g(1), $await(...))

if (x > y && $await(...)) { ... }

尤其是第二行代码,$await可能由于短路而根本不会执行。为此,Jscex要求开发人员明确编写这样的代码:

var a1 = g(1);
var a2 = $await(...);
f(a1, a2);

if (x > y) {
    var flag = $await(...);
    if (flag) { ... }
}

我并不担心这会让开发人员编写代码时有所不便,事实上F#的Async Workflow是有这般要求,我甚至敢保证未来C#的异步特性也是类似的设计。但是,JavaScript有个重要的特点:它在实际使用时往往会被压缩。如果仅仅是去除空白字符,那么Jscex自然还可以正常工作。但事实上现代的JavaScript压缩工具都会分析代码的语义,并重新生成体积更小的代码。例如之前的bubbleSortAsync经过压缩便会成为:

var bubbleSortAsync=eval(Jscex.compile("async",function(a){for(var b=0;b<a.length;b++)for(var c=0;c<a.length-b;c++){var d=$await(compareAsync(a[c],a[c+1]));d>0&&$await(swapAsync(a,c,c+1))}}))

试看d>0&&$wait(...)这段代码,完全就让Jscex无法工作了。为此,我为Jscex开发了AOT编译器(scripts目录下的jscexc.js及JscexExtractor.js文件),即在部署前便对代码进行编译并生成目标代码(之前是在运行时生成代码,即JIT编译)。AOT编译器同样使用JavaScript编写,使用node.js运行,这样便可以直接使用Jscex的编译器实现。与编译器核心不同,AOT编译器使用了最新版的Narcissus来解析代码,这是因为Narcissus能够提供更丰富的解析结果,我可以直接获得整个目标方法的起始和结束地址(不过有bug,我使用时绕开了),自然还包括原始代码,用起来十分方便。至于之前提到的依赖于SpiderMonkey扩展,体积较大,执行速度慢等缺点,对于AOT编译器来说便完全不是问题了。

Jscex的AOT编译器使用起来十分简单:

node jscexc.js --input input_file --output output_file

例如,如果一个文件包含之前的bubbleSortAsync方法,那么经过AOT编译器之后,它的代码便会被替换成为:

var bubbleSortAsync = (function (array) {
    var $_builder_$ = Jscex.builders["async"];
    return $_builder_$.Start(this, function () {
        return $_builder_$.Delay(function () {
            var x = 0;
            return $_builder_$.Loop(
                function () {
                    return x < array.length;
                },
                function () {
                    x++;
                },
                $_builder_$.Delay(function () {
                    return $_builder_$.Delay(function () {
                        var y = 0;
                        return $_builder_$.Loop(
                            function () {
                                return y < (array.length - x);
                            },
                            function () {
                                y++;
                            },
                            $_builder_$.Delay(function () {
                                return $_builder_$.Bind(compareAsync(...), function (r) {
                                    return $_builder_$.Delay(function () {
                                        if (r > 0) {
                                            return $_builder_$.Bind(swapAsync(...), function () {
                                                return $_builder_$.Normal();
                                            });
                                        } else {
                                            return $_builder_$.Normal();
                                        }
                                    });
                                });
                            }),
                            false
                        );
                    });
                }),
                false
            );
        });
    });
})

再进行压缩,便不会产生任何问题了。从表面看起来,编译后的Jscex代码体积大了不少,但是其中大部分为重复架子代码,压缩比例一般也会比较大。使用AOT编译后的代码有以下几个好处:

  • 经过JavaScript压缩器处理后也能正确执行。
  • 运行时只需要加载一个极小的jscex.async.min.js文件(异步核心类库),gzip后大小不到1K。
  • 由于代码在发布前生成,节省了JIT编译的开销。

在我个人看来,目前的Jscex已经可以在一些比较正式的场合中使用了。Jscex功能强大,实现小巧,能够与其它类库同时使用(它只会在全局对象上产生一个Jscex对象),接下来我也会为jQuery或MooTools等著名JavaScript框架/类库提供Jscex的绑定。在此也希望您可以实际使用一下Jscex项目,如果遇到问题请及时与我联系,我会给予您必要的支持。

广告时间:第四届nBazaar技术交流会将于2011年4月23日举行。第四届交流会的形式将略作改变:除了三场演讲(Windows Phone 7、IDE插件开发、单点登陆解决方案设计与实现)之外,本次活动设有嘉宾互动环节,您将有机会和嘉宾就某些话题进行探讨。我们正在收集话题,也希望大家踊跃提问,具体信息详见http://nbazaar.org/。此外,nBazaar技术沙龙的邮件列表已经正式启用,所有用户也已添加完成(之前报名或参加过技术会议)。如果有任何疑问,请邮件至

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

21 条回复

  1. FlashSoft
    61.135.152.*
    链接

    FlashSoft 2011-04-16 09:40:58

    Jscex项目目标很赞,计划使用JIT编译方式来应用到自己项目中去

  2. 链接

    宝玉 2011-04-16 19:01:37

    @FlashSoft

    打算用到新浪微博中么?

    Jscex项目确实很激动人心,不过像我这样的老js程序员还真有点跟不上,使用起来感觉在学习一门新的语言了,一如当初刚接触jQuery!@FlashSoft你还是很能与时俱进嘛!

    另外给@老赵 提一下建议:Jscex的文档(https://github.com/JeffreyZhao/jscex)似乎还偏简单,一般人可能还是看不明白,也许可以找人帮编辑详细一点文档。

  3. 老赵
    admin
    链接

    老赵 2011-04-18 00:10:53

    @FlashSoft

    呵呵,不过JIT方式不适合压缩混淆,所以估计最终还是得AOT。有问题的话和我说一下吧,有问题应该都不大了,最多是些边角料上,比如某个操作符啊,某个异常没处理好之类的,我也正在“劝”组里新来的前端工程师在项目里使用,希望能够经得起检验。

  4. 老赵
    admin
    链接

    老赵 2011-04-18 00:13:04

    @宝玉

    其实我的目的就是让程序员不学习JavaScript以外的东西。Jscex其实除了多了个$await,从语义也好,行为也罢,都是传统的JavaScript,就连$await都是标准的“方法调用”。

    文档正在写新的,写好了就可以像更大范围内推广一下了,嗯嗯。

  5. GuangXiN
    118.186.130.*
    链接

    GuangXiN 2011-04-18 06:24:44

    看过去你写的几篇文章,愣没看懂这个await是什么意思,这个异步又是怎么工作的,现在一说到异步编程,脑子里就浮现出一堆callback,能不能撰写或者推荐几篇通俗一点的异步编程思想方面的文章?

  6. eleven
    59.42.126.*
    链接

    eleven 2011-04-18 08:50:59

    赵先生,您好,我是一名已经工作的学者。原因是这样的,我的专业是通信工程,本科生,但是我是民办学校出来的,其实大学学到的东西很有限,现在虽然找到了一份工作,在一个电信类单位上班,但是我对于现实很不满,因为我做的是一份闲职,既没有工作技术,也不需要用什么脑。我害怕这样下去,三五年后我就跟我的同学们拉开差距了。所以我在寻求改变,一直在想寻找新的“路”。通信我是没兴趣了,好的公司不要我们这样的学生,事实上我也是非常缺乏实际的能力,因为通信设备贵,学校方面不可能真正让你接触到,加上大学懒散,所以所学有限。不过我对计算机倒是蛮有兴趣,考虑再三,我决定走计算机的路,可能不一定会成功,但是我会试试看。了解了一下,听说.NET,java,php都不错,如果我现在重新开始学过,请问我该先学什么再学什么?要买什么书?听说程序员的基础是高等数学、离散数学、计算机原理、操作系统原理、数据结构、算法等,这些其实我也学过不少,只是大学考试以后就丢一边,那我是不是这些书都要重新学过呢?赵先生,哦,不,应该是赵老师,赵老师,我之前想去北大青鸟报名的,差点把我工作一年的积蓄都投入了那里,还好,我心痛痛,就上网查清楚,然后就认识了您。看了你不少文章,虽然有很多看不懂,但是知道您是软件这方面的佼佼者,同时也了解到培训机构的不可信,但是我很烦恼的是入门不知怎么入法,请老师指点一条路,让我有力也知道往哪个方向发。在此衷心谢谢赵老师。

  7. eleven
    59.42.126.*
    链接

    eleven 2011-04-18 08:56:22

    我并不是对于民办学校不满,其实我也知道其他很多大学生都学到的东西不多,就拿我们学校来说,明明是通信班,其实整个四年大学,就只有一个通信类的老师,其他都是电子类的。为什么?通信班热门,所以每个学校都开,但是对于设施和师资,他们是没关系的,东拼西凑,应付学生。

  8. eleven
    59.42.126.*
    链接

    eleven 2011-04-18 09:08:03

    赵老师,有上线的话告诉我一声,我在线等哈。

  9. 老赵
    admin
    链接

    老赵 2011-04-18 11:17:41

    @GuangXiN

    await的作用就是告诉编译器这句代码是异步的,于是它生成代码的时候就会变成回调函数,避免你自己写一大堆乱七八糟的callback,难写也难维护。我最近会详细写一下Jscex的文档的,到时候你应该就清楚了。

  10. 老赵
    admin
    链接

    老赵 2011-04-18 11:19:59

    @eleven

    这就看你对什么感兴趣了,学好了都差不多。

  11. eleven
    59.42.126.*
    链接

    eleven 2011-04-18 13:05:46

    赵老师,中午好。我学过一点C++,如果学.NET,我该学哪些课程?有什么好书介绍一下吗?

  12. 链接

    宝玉 2011-04-19 00:49:58

    @eleven

    您这重点不是去学什么东西,重点是去做点什么东西,技术么,不过是手段!先想做个什么东西,然后针对去学习什么东西,也许这样最适合您这样的纯兴趣导向的。 比如你想做网站就学asp.net,想写应用程序就学C++,想做移动开发就学Java或者Objective-C

  13. 幸存者
    114.80.133.*
    链接

    幸存者 2011-04-19 15:52:08

    这名儿起得不太好啊,至少不方便读和记。好的名称对推广还是很有帮助的,jQuery, Prototype, MooTools 莫不如此啊。

  14. libinqq
    125.95.72.*
    链接

    libinqq 2011-04-19 16:24:02

    赵老师 jscex 是你平时兴趣突起,还是人生脚步的一部分插曲, 还是陪伴你不断更新很久。

  15. libinqq
    125.95.72.*
    链接

    libinqq 2011-04-19 16:34:35

    赵老师帮我指点下我的未来人生路线, 我今年29了,老程序员了,在公司做开发员5年多了。我总是给企业做网站,政府做网站,争的钱不多(我在三线城市,但是我已经安家不能在外出了)。因为总是在做 添加删除查,网页兼容调试,我已经麻木很多年了。公司竞争很厉害,大家都往管理攀,但是管理取决老板欣赏谁,怎么可以让手下好干活,我不是很擅长。公司总是项目式开发,对个人成长不是很大。

    最近我打算从零开始学习, 学习 webkit 浏览器开发或 嵌入式开发。 学习C++。我要抛弃,原来的一切,.net 数据库等, 我想让我程序员的人生在长一些。就我的年龄,你觉得我的风险大么,请指点一二,不胜感谢。

  16. 老赵
    admin
    链接

    老赵 2011-04-20 16:41:26

    @幸存者

    J-Sex,挺好读的啊,哈哈哈。

  17. eleven
    59.42.126.*
    链接

    eleven 2011-04-20 16:41:49

    谢谢宝玉指导,你的回答很有用。

  18. 老赵
    admin
    链接

    老赵 2011-04-20 16:42:17

    @libinqq

    我不懂你说的嵌入式开发,我不懂未来……

  19. 链接

    宝玉 2011-04-21 16:03:01

    @libinqq

    人生不止程序员一条路,管理也不是程序员唯一出路,未必要一棵树上吊死,关键还得看自己适合做什么事情,真正想做什么事情。这个问题还是得常想想。

    我觉得到了30这个年龄,做什么事情目的性要强一些,毕竟不再是年轻的时候,比如你要学C++,要学嵌入式,目的是什么?会对应有什么机会?这些问题想清楚了。

    换一种思考的角度,首先是你未来打算做什么,然后根据需要去学什么,比如你要换个C++工作,那么就可以去学C++,比如你想投身移动互联网,那么可以去学学ios/android,比如你想做管理,那么不妨去学学PMP的课程。

  20. 链接

    Eric Poon 2011-04-29 18:19:38

    compareAsync()是传入$await()的就是需要的回调函数?随之由$await和编译器生成完整的回调脚本,并执行compareAsync()函数?

  21. 老赵
    admin
    链接

    老赵 2011-04-29 22:27:41

    @Eric Poon

    好像没怎么听懂你的意思……

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我