Spiga

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

2011-04-15 02:09 by Jeffrey Zhao, 9714 visits, 收藏, 编辑

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

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项目,如果遇到问题请及时与我联系,我会给予您必要的支持。

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

Add your comment

44 条回复

  1. #1楼 阳光沙滩海岸线      2011-04-15 02:53
    我……看不明白,太深奥了~
     回复 引用 查看   
  2. #2楼 大石头      2011-04-15 07:13
    这个想法非常的好!

    我在想,C#是不是也是可以这样做,运行时控制别的类库的IL代码,并进行优化处理,貌似相当的复杂……
     回复 引用 查看   
  3. #3楼 银光小子      2011-04-15 08:39
    唉 总算 来了
    老赵 觉得博客园 还是适合你些呢
    建议你把 之前那几篇博客都从独立博客那移过来都补上...

    单从博客园关注你的人 还以为你消失不写博客了 呢
     回复 引用 查看   
  4. #4楼 风云      2011-04-15 08:47
    呵呵,好久没有看见老赵高质量的文章了,呵呵
     回复 引用 查看   
  5. #5楼 风云      2011-04-15 08:50
    老赵真是个夜猫子,2点多还在写博客!
     回复 引用 查看   
  6. #6楼 nyth      2011-04-15 08:57
    没看明白,还是不清楚,有什么好处!感觉带个例子会更好!
     回复 引用 查看   
  7. #7楼 小刚qq      2011-04-15 08:59
    支持..>!!!
     回复 引用 查看   
  8. #8楼 testzhangsan      2011-04-15 09:08
    引用风云:老赵真是个夜猫子,2点多还在写博客!

    做技术的,都是要技术不要命得人!
     回复 引用 查看   
  9. #9楼 风云      2011-04-15 09:11
    引用testzhangsan:
    引用风云:老赵真是个夜猫子,2点多还在写博客!

    做技术的,都是要技术不要命得人!

    呵呵,当年那个时代已经过去了,呜呜!
     回复 引用 查看   
  10. #10楼 Fred_Xu      2011-04-15 09:27
    持续围观老赵的文章!
     回复 引用 查看   
  11. #11楼 假正经哥哥      2011-04-15 09:32
    呵呵,不太理解,这个的实际应用情况
     回复 引用 查看   
  12. #12楼 chenkai      2011-04-15 09:42
    @testzhangsan
    @风云
    还是学会保重身体.昨天在北京IT圈里有一个很有名气的IT编辑 过劳死.同样是一位Developer。.年仅28岁. 真是英年早逝...啊.....
     回复 引用 查看   
  13. #13楼 道法自然      2011-04-15 09:52
    支持一下,虽然对JS一窍不通,不过以后可以给自己多一些参考的思想了。
     回复 引用 查看   
  14. #14楼 huyong      2011-04-15 10:07
    文章不错,感谢作者分享呀!
    随便有时间就光临:
    cnBlobs:http://www.cnblogs.com/huyong/
    CSDN: http://blog.csdn.net/chinahuyong
     回复 引用 查看   
  15. #15楼[楼主] Jeffrey Zhao      2011-04-15 10:27
    引用银光小子:
    唉 总算 来了
    老赵 觉得博客园 还是适合你些呢
    建议你把 之前那几篇博客都从独立博客那移过来都补上...

    单从博客园关注你的人 还以为你消失不写博客了 呢

    你能说清为什么博客园更适合我我就回来,哈哈。
     回复 引用 查看   
  16. #16楼[楼主] Jeffrey Zhao      2011-04-15 10:28
    引用nyth:没看明白,还是不清楚,有什么好处!感觉带个例子会更好!

    文章里的链接就是例子啊,比如排序算法动画。
     回复 引用 查看   
  17. #17楼 刀 刀      2011-04-15 10:52
    这个是项目中要用的吗?还是自己写的一个开源类库,跟项目没关系?发现自己JS好差,正在恶补中。
     回复 引用 查看   
  18. #18楼 winter-cn      2011-04-15 10:53
    赵老 我还是强烈要求发布时编译
     回复 引用 查看   
  19. #19楼 winter-cn      2011-04-15 10:55
    引用Jeffrey Zhao:
    引用银光小子:
    唉 总算 来了
    老赵 觉得博客园 还是适合你些呢
    建议你把 之前那几篇博客都从独立博客那移过来都补上...

    单从博客园关注你的人 还以为你消失不写博客了 呢

    你能说清为什么博客园更适合我我就回来,哈哈。

    博客园有首页 看的人多
     回复 引用 查看   
  20. #20楼[楼主] Jeffrey Zhao      2011-04-15 11:30
    引用winter-cn:赵老 我还是强烈要求发布时编译

    AOT编译器不就是发布时编译吗?
     回复 引用 查看   
  21. #21楼[楼主] Jeffrey Zhao      2011-04-15 11:31
    引用刀 刀:这个是项目中要用的吗?还是自己写的一个开源类库,跟项目没关系?发现自己JS好差,正在恶补中。

    项目中不用写类库做什么啊……
     回复 引用 查看   
  22. #22楼 McJeremy&Fan      2011-04-15 11:54
    非常赞啊,有木有!
    倒是看明白了怎么用这个异步。
    但是看源码时,还是比较迷糊:
    1、 Jscex.Async 以及 Jscex.builders["async"] 下的对象,基本都有一个名为“start"的function对象。 它们之间的调用关系,及参数的传递过程,看得我比较晕。只稍微看明白了一条线:
    那就是Jscex.Async的start调用的是Jscex.builders["async"] 中的Start返回的对象中的start方法,而这个start方法,调用的又是Bind中返回的对象的start方法。(有点绕口啊,有木有!),而什么时候Bind,则是在我们自己写的代码里的$await来标识了。而大多数情况下:Jscex.builders["async"]中的Normal、Return、Break有着关键作用,据我个人臆测,这几个是回调不断返回的关键。
    2、Jscex.builders对象(或者 说数组)中只定义了一个"async",那为什么使用"$async"也可以识别呢? 这个不太明白。

    ------------------------------------
    ----- 不知道老赵这个项目是否会作为商用? 如果不作为商用的话,不知道老赵可不可以对代码进行讲解讲解呢(或者在源码中加点注释也行撒)。
    ------------------------------------
     回复 引用 查看   
  23. #23楼 银光小子      2011-04-15 12:59
    @Jeffrey Zhao
    1:你看把 你刚来 博客园就把你的文章放头条了
    足见博客园官方对你的重视


    2:你看把 你刚来 这么多群众就来围观了
    足见博客园民众对你的重视


    3: 没必要为了自己的小树木放弃这片森林
    其实能懂你 天底下或许谁人都会有这个想法,一旦自己能力到位,都会想另辟土壤,独立门户。享受自己的小世界。
    可我个人认为你这样得不偿失。
    因为,博客园这个平台,不垃圾。不百度,不腾讯,不天涯,不猫扑。技术社区能到这个地步,实乃.NET技术之福。
    而你仅为自己那个~~的想法,就离开这么个大平台,何必呢??


    4:能力越大,责任越大
    维护.NET技术的尊严,推广编程之美的理念,劝众~宁自学不要被北大所骗,以一己之力影响中国.NET社区....
    嘿嘿,这是你的理念对吧....
    既然如此,责任来了,就要背起来。对于你和各位大牛来说,履行责任的唯一方式或许就是写好的博让更多人看到....
    你开独立博客大片大片的人不会看或者看不到,谈何影响,谈何责任???

    嘿嘿
    记得 当时别圈外人问我老赵是谁,我实在不知道怎么说了,就问,
    你知道仓#空吗??
    --嗯知道
    老赵于.NET社区就像仓#空于日本AV....
    --噢 老赵就是.NET社区的苍井空对吧....

    --那他的作品是不是让人看了很上瘾很细致很精致啊......



     回复 引用 查看   
  24. #24楼 alloc[未注册用户]2011-04-15 13:07
    23楼的哥们,我服了
     回复 引用   
  25. #25楼[楼主] Jeffrey Zhao      2011-04-15 13:07
    @银光小子
    话说,博客园对我来说最大的问题就是系统没法订制,就这一点我就没法在博客园,乃至任何一个独立的博客提供商写博客。
    所以博客园最好有办法能够聚合其他博客内容,然后做到评论集成,就再好不好多了。
     回复 引用 查看   
  26. #26楼[楼主] Jeffrey Zhao      2011-04-15 13:08
    @McJeremy&Fan
    其实你不用看懂编译后的代码,编译后的代码是给机器看不是给程序员看的,程序员看的是编译前的代码。
    这是个开源项目,会有详细说明的。至于$async什么是旧的实现,现在已经是async了。
     回复 引用 查看   
  27. #27楼 Everlonely      2011-04-15 13:59
    这里的人气比你目前的独立博客的人气要好很多啊
     回复 引用 查看   
  28. #28楼 Everlonely      2011-04-15 14:03
    很多人都不明白Jscex是做什么的,猜测可能性有2:
    1.大众水平跟老赵相差太远
    2.项目应用范围比较狭小
     回复 引用 查看   
  29. #29楼[楼主] Jeffrey Zhao      2011-04-15 14:20
    引用Everlonely:
    很多人都不明白Jscex是做什么的,猜测可能性有2:
    1.大众水平跟老赵相差太远
    2.项目应用范围比较狭小

    应用范围绝对大,不过这东西的思路太创新了是真的,哈哈。
     回复 引用 查看   
  30. #30楼[楼主] Jeffrey Zhao      2011-04-15 14:20
    引用Everlonely:这里的人气比你目前的独立博客的人气要好很多啊

    那是一定的,一个是博客园的人气啊,大哥……
     回复 引用 查看   
  31. #31楼 McJeremy&Fan      2011-04-15 14:32
    引用Jeffrey Zhao:
    @McJeremy&Fan
    其实你不用看懂编译后的代码,编译后的代码是给机器看不是给程序员看的,程序员看的是编译前的代码。
    这是个开源项目,会有详细说明的。至于$async什么是旧的实现,现在已经是async了。


    --呵呵。
    我并不是要看懂编译后的, 而是想跟踪并了解一下这个项目的关于异步的实现过程。
     回复 引用 查看   
  32. #32楼 代码乱了      2011-04-15 15:02
    AOT是什么?还不太明白哦
    是基于什么平台的编译器?
     回复 引用 查看   
  33. #33楼[楼主] Jeffrey Zhao      2011-04-15 15:22
    @代码乱了
    跟JIT一起的AOT,总归该知道是什么了吧,hoho。
     回复 引用 查看   
  34. #34楼[楼主] Jeffrey Zhao      2011-04-15 15:22
    @McJeremy&Fan
    会有说明的。
     回复 引用 查看   
  35. #35楼 崔鹏飞      2011-04-15 16:06
    引用chenkai:
    @testzhangsan
    @风云
    还是学会保重身体.昨天在北京IT圈里有一个很有名气的IT编辑 过劳死.同样是一位Developer。.年仅28岁. 真是英年早逝...啊.....

    靠,这么夸张?
     回复 引用 查看   
  36. #36楼 chenkai      2011-04-15 16:09
    @崔鹏飞
    真实事情,也是昨天晚上才知道的....
     回复 引用 查看   
  37. #37楼 在别处      2011-04-15 23:28
    js编译js?
     回复 引用 查看   
  38. #38楼[楼主] Jeffrey Zhao      2011-04-15 23:34
    @在别处
    你得到了它。
     回复 引用 查看   
  39. #39楼 草头蕾      2011-04-16 00:25
    我了个去。。js异步编程。。老赵好久没上新博,一上就是这么猛的。。。还有,同意23楼的说法。
     回复 引用 查看   
  40. #40楼 skyaspnet      2011-04-16 14:34
    @Jeffrey Zhao
    最简单的就是大家经常上园子,而不习惯于单独上某一个BLOG,赵大哥还是回来吧,回来吧。。。
     回复 引用 查看   
  41. #41楼 贺臣      2011-04-18 10:26
    赵姐夫 每次一来都有一段广告!这种宣传方式不错,也算是一种营销方式
     回复 引用 查看   
  42. #42楼 HEllow777[未注册用户]2011-04-19 12:03
    老赵,你举办的技术沙龙怎么获取信息啊?什么时候,什么地点,?
     回复 引用   
  43. #43楼[楼主] Jeffrey Zhao      2011-04-19 15:53
    @HEllow777
    链接里不是写着吗?
     回复 引用 查看   
  44. #44楼 Eysaa[未注册用户]2011-04-19 17:36
    你的QQ多少啊?我关注你围脖,你同事有跟你提起过吧。
     回复 引用   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 2016645 npRbeMsS9ss=