當(dāng)瀏覽器觸發(fā)DOMContentLoaded事件時(shí),Angular就開始工作。它首先尋找ng-app指令。如果瀏覽器在DOM中找到ng-app指令,它會(huì)為我們自動(dòng)啟動(dòng)應(yīng)用。如果沒有找到這個(gè)指令,Angular期望我們自己手動(dòng)啟動(dòng)應(yīng)用。
要手動(dòng)啟動(dòng)一個(gè)AngularJS應(yīng)用,可以使用Angular的bootstrap()方法。在一些罕見的情況下手動(dòng)啟用應(yīng)用程序是有意義的。例如,想要在某個(gè)其他庫的代碼運(yùn)行之后,或者在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建元素時(shí),啟動(dòng)AngularJS應(yīng)用。要想手動(dòng)啟動(dòng)應(yīng)用,可以像下面這樣啟動(dòng)它:
var newElement = document.createElement("div");
angular.bootstrap(newElement, ['myApp']);
如果在DOM中沒有找到ng-app指令,而且也沒有手動(dòng)啟動(dòng)應(yīng)用,則AngularJS不會(huì)運(yùn)行。忘記在頁面中引入ng-app指令肯定會(huì)引發(fā)一些嚴(yán)重的問題。
bootstrap()方法只允許我們啟動(dòng)angular應(yīng)用一次。
如果在ng-app屬性中沒有指定應(yīng)用程序,則Angular會(huì)加載一個(gè)不帶特定模塊的應(yīng)用。如果指定了,Angular就會(huì)加載與這個(gè)指令關(guān)聯(lián)的模塊。
一旦應(yīng)用程序加載完成,$injector就會(huì)在應(yīng)用程序的$rootScope旁邊創(chuàng)建$compile服務(wù)。
$rootScope創(chuàng)建后,$compile服務(wù)就會(huì)接管它。它會(huì)將$rootScope與現(xiàn)有的DOM連接起來,然后從將ng-app指令設(shè)置為祖先的地方開始編譯DOM。
視圖的工作原理
當(dāng)瀏覽器在標(biāo)準(zhǔn)的Web流程中獲取HTML時(shí),它會(huì)收到HTML代碼并將它解析為一個(gè)DOM樹。這個(gè)DOM樹中的每個(gè)元素被稱作DOM元素,這些DOM元素會(huì)構(gòu)建一堆節(jié)點(diǎn)。然后瀏覽器負(fù)責(zé)繪制出這個(gè)DOM樹的結(jié)構(gòu)。
瀏覽器在提取腳本時(shí)(從<script>標(biāo)簽中),會(huì)暫停DOM解析并等待腳本取回(你可以修改這一行為,但是這個(gè)行為是默認(rèn)的)。
當(dāng)angular.js被取回時(shí),瀏覽器會(huì)執(zhí)行它,同時(shí)設(shè)置一個(gè)事件監(jiān)聽器來監(jiān)聽瀏覽器的DOMContentLoaded事件。DOMContentLoaded事件會(huì)在HTML文檔加載完成并開始解析時(shí)觸發(fā)。
檢測(cè)到這個(gè)事件時(shí),Angular會(huì)查找ng-app指令,然后創(chuàng)建運(yùn)行需要的一系列必要的組件(即$injector、$compile服務(wù)以及$rootScope),然后開始解析DOM樹。
編譯階段
$compile服務(wù)會(huì)遍歷DOM樹并搜集它找到的所有指令,然后將所有這些指令的鏈接函數(shù)合并為一個(gè)單一的鏈接函數(shù)。
然后這個(gè)鏈接函數(shù)會(huì)將編譯好的模板鏈接到$rootScope中(也就是附屬于ng-app所在的DOM元素的作用域)。
它會(huì)通過檢查DOM屬性、注釋、類以及DOM元素名稱的方式查找指令。
$compile服務(wù)通過遍歷DOM樹的方式查找有聲明指令的DOM元素。當(dāng)碰到帶有一個(gè)或多個(gè)指令的DOM元素時(shí),它會(huì)排序這些指令(基于指令的priority優(yōu)先級(jí)),然后使用$injector服務(wù)查找和收集指令的compile函數(shù)并執(zhí)行它。
指令中的compile函數(shù)會(huì)在適當(dāng)?shù)臅r(shí)候處理所有DOM轉(zhuǎn)換或者內(nèi)聯(lián)模板,如同創(chuàng)建模板的副本。
// 返回一個(gè)鏈接函數(shù)
var linkFunction = $compile(appElement);
// 調(diào)用鏈接函數(shù),將$rootScope附加給DOM元素
linkFunction($rootScope);
每個(gè)節(jié)點(diǎn)的編譯方法運(yùn)行之后,$compile服務(wù)就會(huì)調(diào)用鏈接函數(shù)。這個(gè)鏈接函數(shù)為綁定了封閉作用域的指令設(shè)置監(jiān)控。這一行為會(huì)創(chuàng)建實(shí)時(shí)視圖。
最后,在$compile服務(wù)完成后,AngularJS運(yùn)行時(shí)就準(zhǔn)備好了。
運(yùn)行時(shí)
在標(biāo)準(zhǔn)的瀏覽器流程中,事件循環(huán)會(huì)等待事件執(zhí)行(比如鼠標(biāo)移動(dòng)、點(diǎn)擊、按鍵等)。當(dāng)這些事件發(fā)生時(shí),它們會(huì)被放到瀏覽器的事件隊(duì)列中。如果有函數(shù)處理程序?qū)κ录鞒鲰憫?yīng),瀏覽器就會(huì)將event對(duì)象作為參數(shù)來調(diào)用這些事件處理程序。
ele.addEventListener('click', function(event) {});
Angular中對(duì)事件循環(huán)做了一點(diǎn)增強(qiáng),并且Angular還提供了自己的事件循環(huán)。指令自身會(huì)注冊(cè)事件監(jiān)聽器,因此當(dāng)事件被觸發(fā)時(shí),指令函數(shù)就會(huì)運(yùn)行在AngularJS的$digest循環(huán)中。
Angular的事件循環(huán)被稱作$digest循環(huán)。這個(gè)$digest循環(huán)由兩個(gè)小型的循環(huán)組成,分別是evalAsync循環(huán)和$watch列表。
當(dāng)事件被觸發(fā)時(shí),事件處理程序就會(huì)在指令的上下文中進(jìn)行調(diào)用,也就是AngularJS的上下文中。從功能上講,AngularJS會(huì)在包含作用域的$apply()方法內(nèi)調(diào)用指令。Angular是在$rootScope上啟動(dòng)$digest循環(huán)時(shí)開始整個(gè)過程的,并且還會(huì)傳播到所有子作用域中。
Angular進(jìn)入$digest循環(huán)時(shí),會(huì)等待$evalAsync隊(duì)列清空,然后再將回調(diào)執(zhí)行上下文交還給瀏覽器。這個(gè)$evalAsync用于在瀏覽器進(jìn)行渲染之前,調(diào)度需要運(yùn)行在當(dāng)前楨棧之外的所有任務(wù)。
此外,$digest循環(huán)還會(huì)等待$watch表達(dá)式列表,它是一個(gè)可能在上一次迭代過程中被改變的潛在的表達(dá)式數(shù)組。如果檢測(cè)到變化,就調(diào)用$watch函數(shù),然后再次查看$watch列表以確保沒有東西被改變。
注意,對(duì)于$watch列表中檢測(cè)到的任何變化,AngularJS都會(huì)再次查看這個(gè)列表以確保沒有東西被改變。
一旦$digest循環(huán)穩(wěn)定下來,并且檢測(cè)到?jīng)]有潛在的變化了,執(zhí)行過程就會(huì)離開Angular上下文并且通常會(huì)回到瀏覽器中,DOM將會(huì)被渲染到這里。
整個(gè)流程在每個(gè)瀏覽器事件之間都會(huì)發(fā)生,這也是Angular如此強(qiáng)大的原因。它還可以將來自瀏覽器的事件注入到AngularJS流程中。