[TOC]
# 1.引導之前:庫階段
在示例中,我們定義了一個指令ez-duang, 它應該會展開成一個動畫 顯示出來。 AngularJS代碼:
~~~
angular.module("ezstuff",[])
.directive("ezDuang",function(){
return {
restrict : "E",
template : "<img src='http://ww4.sinaimg.cn/bmiddle/757eb2ffjw1eptcr4qobjg209205dthh.gif'>"
};
});
~~~
應該在下面看到一幅動畫才對!
但是,看起來沒有什么動畫顯示出來。AngularJS似乎沒有工作,為什么?
有點像操作系統,AngularJs也有一個啟動引導的概念。
當你在HTML文件中引入angular.min.js時,AngularJS只是建立了一個全局的 angular對象,這個對象有一些方法可供開發者調用,但應用的框架還沒有建立。
在這個階段,AngularJS還只是一個庫,和jQuery類似,你可以使用angular.element() 操作DOM,也可以使用angular.injector()創建注入器... 但是,你定義的指令,你 創建的控制器,你封裝的服務,你開發的模板...所有這些組件,還靜靜地躺在那里, 沒有被整合在一起。
我們說,框架還沒有運轉起來,現在還是庫階段。
只有通過啟動引導,AngularJS框架才開始將那些組件拼接在一起,應用才真正 開始運轉。
像下面這樣,試著給html元素增加一個ng-app指令,再重新運行!
~~~
<html ng-app="ezstuff">
....
</html>
~~~
# 2.自動引導啟動框架
就像你看到的那樣,如果HTML模板中有某個標簽有ng-app屬性,那么當DOM樹建立成功后, AngularJS就會自動進入引導過程,啟動整個框架:

試著把ng-app指令挪到body元素上,看看有什么不同?
# 3.手工引導啟動框架
在大多數情況下,我們都使用ng-app指令來進行自動引導啟動,但是如果一個HTML文件中 有多個ng-app,AngularJS只會自動引導啟動它找到的第一個ng-app應用,這是需要手工引導 的一個應用場景。
我們可以利用 angular.bootstrap() 方法進行手動引導:
angular.bootstrap(element, [modules], [config]);
bootstrap方法有三個參數:
1. element : 一個DOM元素,以這個元素為Angular應用的根,等同自動引導時ng-app所在 的元素。這個參數是必須的。比如:document、document.body等。
2. modules : 引導時需要載入的模塊數組。比如:[]、["ezstuff"]等。由于我們的HTML中引用 了ezstuff模塊中定義的ez-duang指令,所以,我們需要指定載入ezstuff模塊。
3. config :引導配置項,可選。我們先忽略。
最終,我們使用如下的形式進行手動引導:
`angular.bootstrap(document,["ezstuff"]);` 改進的代碼已經預置于在線課程。請點擊【手動引導】按鈕啟動引導過程!
# 4.引導第1步:創建注入器
引導過程使AngularJS從庫轉變成了一個框架。
回憶我們之前提到,AngularJS深入骨髓地使用著依賴注入,那么,在引導過程 之初,首先需要創建一個注入器就毫不奇怪了。
注入器是通向AngularJS所有功能的入口,而AngularJS的功能實現,是通過模塊的方式組織的。所以, 在創建注入器的時候,需要告訴AngularJS載入哪些模塊(ng模塊是內置載入的,不需要顯式指定)。

在自動啟動引導的場景下,可以給ng-app賦值以指定一個需要載入的模塊,比如: ng-app = "ezstuff"
在手動啟動引導的場景下,通過bootstrap方法的第二個參數指定需要載入的模塊,比如: angular.bootstrap(document,["ezstuff"]);
INSIDE:無論自動啟動還是手工啟動,最終都是調用angular對象上的injector()方法創建了一個 注入器,然后把這個注入器存入了根對象的data里:
~~~
var injector = angular.injector(["ng","ezstuff"]);
angular.element(document).data("$injector",injector);
~~~
# 5.引導第2步:創建根作用域
scope對象是AngularJS實現數據綁定的重要服務,所以,在引導啟動建立了注入器之后, AngularJS馬上在應用的根節點上創建一個根作用域:$rootScope對象。

如果是自動引導啟動,那么ng-app所在的DOM節點對應著根作用域。如果是手工引導啟動, 那么在bootstrap方法中指定的第一個參數就對應著根作用域。
無論哪一種情況,一旦$rootScope對象創建成功,AngularJS就將這個對象存儲到根節點 的data中,我們可以使用如下的方法查看這個對象: angular.element(approot).data("$rootScope");
你可以擺弄一下代碼,看看$rootScope到底是什么東西。
# 6.引導第3步:編譯DOM子樹
引導過程的最后一步,是以ng-app所在DOM節點為根節點,對這棵DOM子樹進行編譯。

編譯過程通常借助于指令,完成這幾種操作:
對DOM對象進行變換。
在DOM對象上掛接事件監聽。
在DOM對象對應的scope對象上掛接數據監聽。 編譯過程是AngularJS相當有特點的一個存在,我們將在下一節繼續深入。
現在你應該看到結果了吧?
# 7.編譯器/$compile
編譯器$compile是一個AngularJS的內置服務,它負責遍歷DOM樹來查找匹配指令, 并調用指令的實現代碼進行處理。
HTML編譯包括3個步驟:
1. **匹配指令** $compile遍歷DOM樹,如果發現有元素匹配了某個指令,那么這個指令將被加入 該DOM元素的指令列表中。一個DOM元素可能匹配多個指令。
2. **執行指令的編譯函數** 當一個DOM元素的所有指令都找齊后,編譯器根據指令的優先級/priority指令進行排序。 每個指令的compile函數被依次執行。每個compile執行的結果產生一個link函數,這些 link函數合并成一個復合link函數。
3. **執行生成的鏈接函數** $compile通過執行指令的link函數,將模板和scope鏈接起來。結果就是一個DOM視圖和scope對象模型 之間的動態數據綁定。
## 為何將編譯和連接兩個步驟分開?
簡單說,當數據模型的變化會導致DOM結構變化時,指令就需要分別定義compile()函數和link函數。 例如,ng-repeat指令需要為數據集合中的每個成員復制DOM元素。將編譯和鏈接過程分開可以有效 地提高性能,因為DOM的復制放在compile()里,僅需要執行一次,但鏈接則發生在每個生成的DOM元素 上,所以指令的link()函數會執行多次。
指令很少需要compile函數,因為大多數指令考慮的是作用于特定的DOM元素實例,而不是改變DOM 的結構。所以link函數更常用。
# 8.指令/directive
籠統地說,指令是DOM元素(例如屬性、元素、CSS類等)上的標記符,用來告訴AngularJS的HTML編譯器 ($compile服務)將特定的行為綁定到DOM元素,或者改變DOM元素。
指令可以放置在元素名、屬性、CSS類名稱及備注中。下面是一些等效的觸發"ng-bind"指令的寫法:
~~~
<span ng-bind="exp"></span>
<span class="ng-bind: exp;"></span>
<ng-bind></ng-bind>
<!-- directive: ng-bind exp -->
~~~
指令的實現本質上就是一個類工廠,它返回一個指令定義對象,編譯器根據這個指令定義對象進行操作。

問題是,HTML中的ez-duang,怎么就匹配到了JavaScript中的ezDuang?
9.指令的規范化
AngularJS在進行匹配檢測之前,首先對HTML元素的標簽和屬性名轉化成規范的駝峰式字符串:
1. 去除名稱前綴的x-和data-
2. 以: , - 或 _ 為分割符,將字符串切分成單詞,除第一個單詞外,其余單詞首字母大寫
3. 重新拼接各單詞 例如,下面的寫法都等效地匹配ngBind指令:
~~~
<span ng-bind="name"></span> <br>
<span ng:bind="name"></span> <br>
<span ng_bind="name"></span> <br>
<span data-ng-bind="name"></span> <br>
<span x-ng-bind="name"></span> <br>
~~~
所以,在前面的課程中,我們在HTML中使用的`ez-duang`指令,將被規范為ezDuang, 編譯器使用這個規范化的名稱與注冊的指令進行匹配。
- 步入JavaScript的世界
- 二進制運算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的產生與發展
- DOM事件處理
- js的并行加載與順序執行
- 正則表達式
- 當遇上this時
- Javascript中apply、call、bind
- JavaScript的編譯過程與運行機制
- 執行上下文(Execution Context)
- javascript 作用域
- 分組中的函數表達式
- JS之constructor屬性
- Javascript 按位取反運算符 (~)
- EvenLoop 事件循環
- 異步編程
- JavaScript的九個思維導圖
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得關注的庫===
- ==文章==
- JavaScript框架
- Angular 1.x
- 啟動引導過程
- $scope作用域
- $q與promise
- ngRoute 和 ui-router
- 雙向數據綁定
- 規范和性能優化
- 自定義指令
- Angular 事件
- lodash
- Test