從<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版本的。