从<code>源代码</code>到<code>浏览器中可运行的程序</code>之间的过程都可以被认为是compile过程,在angular程序中,源代码中可能包含@directive、@component、@ngmodule、@pipe等各种内容,无论是typescript的annotation还是template中的double binding,这些最后都会变成可被浏览器解析的语言运行起来。
我们可以将整个compile过程简化为:
<code>inputs(源代码)-----parser(解析器)----->instantiate(实例化)</code>
在后面的文章中我们逐步来介绍这三部分在angular中具体的工作和原理
由于篇幅有限,我们以component和directive的组合为例来进行介绍
hello.component.ts
hello_comp.html
首先在hellocomp中定义了user的初始化值,并在template中渲染出来,在template中还包含了ngmodel绑定的input。
我们将directive代码也添加进来,directive的selector支持css选择器,当在template代码中发现符合css选择器中的element时,就会实例化相应directive。
以上的简化代码也很容易理解,form和[ngmodel]的selector分别在<code><form></code>标签和带有<code>ngmodel</code>的attribute标签中生成了对应的directive实例。值得一提的是在ngmodel的directive中依赖了ngform,这意味着ngmodel的实例将在template的父元素中查找form依赖,直到命中为止。
以上我们已经明确了原始代码的所有功能,这些被定义component和directive正是我们compiler的inputs,下面就来介绍compiler对代码的parse过程
再关注一下之前的hello_comp.html
被parser翻译后的template应该对compiler更加友好,以ast(abstract syntax tree)的方式对template中的html重新组织之后,我们可以获得以下的json数据
以上的数据表示了html,相当简单易懂。而template中的binding可以用以下的json表示
<code>text</code>代表着初始化的数据,因为依赖ts代码中的输入,所以默认为空。<code>expr</code>包含着angular程序中在template的表达式信息,<code>proppath</code>中包含着数据的路径,当在expression中使用pipe或者*ngfor等代码时 ,<code>expr</code>中包含的内容会有更复杂的表现,<code>line</code>和<code>col</code>保留了binding中原始的位置信息,这点很重要,当template报错的时候可以精确告诉开发者template中哪一行代码发生了问题,如果你开发过angular 2程序,你一定见过这种报错:
现在我们的parser已经可以解析出component的内容了,对于directive又该如何表示呢,我们仍然可以在ast json中进行表示
以上我们已经将所有的代码parse成了对compiler友好的ast json格式,下一步就是将parse得到的数据进行实例化,让app可以真正运行起来。
首先介绍ngelement的数据结构,ngelement是angular 2中很重要的一部分,负责将ast转化回dom结构,并完成相应的binding和directive等内容的实例化。
ngelement中的这部分代码相当简单,生成了ast json中对应的dom结构,并添加了对应attribute,针对directive部分的处理如下,逐层实例化ast json中的ctor和deps
针对于template中的binding部分,通过ast json中的expr表达式来进行脏值检查,并将数据存储在target中,在angular 2中最简单取出该值的方式就是<code><div #testdiv></div></code>,<code>testdiv</code>就是binding中的target,所有的数据都会存储在target中。
最后我们会有view类来整合ngelement和binding中的脏值检查
通过以上的步骤,我们可以将parser生成的ast转化为可以运行的app,然而compiler的功能不仅仅是将源代码转换ast再转换为可运行程序,在compile的过程中对性能进行优化也是很重要的一步。
在ngelement对directive处理的代码中,我们看到其中directives的类型是map,如果我们将所有的directives都列举出来,将代码转换为
我们将ngelement转换为inlinengelement以获得更高的性能,然而我们的view类中却仍然含有大量的array,如何让view也利用v8虚拟机的fast property优化,其实方法也很明确:我们只需要按正确的顺序初始化dom,并且在directive的初始化过程中也依照正确的顺序,保证被依赖的directive先被初始化生成就可以了。
初始化dom结构
初始化directive
再讲binding中的dirtycheck对应到相应的node
通过以上的步骤,我们view全部可以利用fast property特性进行优化,当然所有的component的代码都需要根据component中directive和expression等内容单独生成,我们需要针对每个component生成单独的compile代码
在以上的代码中,我们展示了angular的parser和instantiate是如何协同工作的,通过优化instantiate的代码,利用v8虚拟机的性能优化,angular 2再次获得了将近1倍的性能提升。
然而有一个问题被我们忽略了,我们应该使用什么作为<code>parser</code>? 使用浏览器是个很好的主意,浏览器很适合用于解析html,在angular 1和angular 2中我们也的确可以使用浏览器作为parser,这也就是jit(just in time)编译的部分,所有的compile过程全都是在浏览器端进行的。

如果我们可以将compileelement的过程放在server端,那浏览器端承载的工作量就会大幅度减少,相应的页面加载时间也会大幅度减少
用作者手中的一个angular项目比对一下jit和aot的性能
jit compile
aot compile
效果感人
don’t use require statements for your templates or styles, use styleurls and templateurls, the angular2-template-loader plugin will change it to require at build time.
don’t use default exports.
don’t use form.controls.controlname, use form.get(‘controlname’)
don’t use control.errors?.someerror, use control.haserror(‘someerror’)
don’t use functions in your providers, routes or declarations, export a function and then reference that function name
inputs, outputs, view or content child(ren), hostbindings, and any field you use from the template or annotate for angular should be public
尾巴
另外一个问题就是3.0版本去哪了,一路从rc版本使用angular 2.0的用户都知道@angular/router曾经废弃掉了一个版本,这样目前的版本号就变得很尴尬,<code>@angular/core</code>,<code>@angular/compiler</code>,<code>@angular/http</code>等版本号都是保持一致的,而<code>@angular/router</code>的版本号却永远高出一个版本,当主版本号是2.3.1时,router的版本号却已经是3.3.1了,为了保持版本一致,angular将越过3.0版本直接统一从4.0开始。
为了避免各种angular版本号给开发者造成不必要的误解,也为了避免整个社区割裂,angular团队号召大家在非必要情况下忽略版本号,比如:我是一个angular开发者,这是一个angular会议,angular的生态系统发展很快等等。在培训和介绍的时候使用版本号,例如本文介绍的内容就是针对于angular 2版本的。