AngularJS的主要組成部分是:
啟動
我們通過一個例子來講解啟動這個部分
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>
</head>
<body>
<p ng-init=" name='World' ">Hello {{name}}!</p>
</body>
</html>
1.浏覽器載入HTML,然後把它解析成DOM樹。
2.浏覽器載入angular.js腳本。
3.AngularJS等到DOMContentLoaded事件觸發執行。
4.AngularJS尋找ng-app指令,這個指令指示了應用程序的邊界。
5.使用ng-app中指定的模塊來配置注入器($injector)。
6.注入器($injector)是用來創建“編譯服務($compile service)”和“根作用域($rootScope)”的。
7.編譯服務($compile service)是用來編譯DOM樹並把它鏈接到根作用域($rootScope)的,這裡的根作用域就是html。
8.ng-init指令將“World”賦給作用域裡的name這個變量。
9.作用域中的name與頁面上的{{name}}綁定,整個表達式變成了“Hello World”。
執行期
浏覽器的事件機制:
1.浏覽器的Event loop等待事件的觸發。所謂事件包括用戶的交互操作、定時事件、或者網絡事件(服務器的響應)。
2.事件觸發後,如果有綁定事件回調函數,那麼此函數就會被執行。此時會進入Javascript上下文。通常回調用來修改DOM結構。
3.一旦回調執行完畢,浏覽器就會離開Javascript上下文,並且根據DOM的修改重新渲染視圖。
而AngularJS通過使用自己的Event loop,改變了傳統的Javascript工作流。這使得Javascript的執行被分成原生部分和擁有AngularJS執行上下文的部分。只有在AngularJS執行上下文中運行的操作,才能享受到AngularJS提供的數據綁定,異常處理,資源管理等功能和服務。你可以使用 $apply()方法,從普通Javascript上下文進入AngularJS執行上下文。記住,大部分情況下(如在控制器,服務中),$apply都已經被執行過了。只有當你使用自定義的事件回調或者是使用第三方類庫的回調時,才需要自己執行$apply。
下面通過一個例子來講解如何實現“將用戶輸入綁定到視圖上”的效果。
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>
</head>
<body>
<input ng-model="name">
<p>Hello {{name}}!</p>
</body>
</html>
在編譯階段:
input元素上的ng-model指令會給<input>輸入框綁定keydown事件;
{{name}}這個變量替換表達式建立了一個 $watch ,來接受 name 變量改變的通知。
在執行期階段:
按下任何一個鍵(以X鍵為例),都會觸發一個 input 輸入框的keydown事件;
input 上的指令捕捉到 input 內容的改變,然後調用 $apply("name = 'X';")來更新處於AngularJS執行上下文中的模型;
AngularJS將 name='X'應用到模型上;
$digest 循環開始;這個循環是由兩個小循環組成的,這兩個小循環用來處理$evalAsync隊列和$watch列表。這個$digest循環直到模型“穩定”前會一直迭代。這個穩定具體指的是$evalAsync列表為空,並且$watch列表中檢測不到任何改變了。這個$evalAsync隊列是用來管理那些“視圖渲染前需要在當前棧外執行的操作”。這通常使用 setTimeout(0)來完成的。並且,因為浏覽器會根據事件隊列按順序渲染視圖,這時還會造成視圖的抖動。$watch列表是一個表達式的集合,這些表達式可能是自上次迭代後發生了改變的。如果檢測到了有改變,那麼$watch函數就會被調用,它通常會把新的值更新到DOM中。
$watch 列表檢測到了name值的變化,然後通知 {{name}}變量替換的表達式,這個表達式負責將DOM進行更新;
AngularJS退出執行上下文,然後退出Javascript上下文中的keydown事件;
浏覽器以更新的文本重新渲染視圖。
作用域(Scope)
作用域是用來檢測模型的改變和為表達式提供執行上下文的。它是分層組織起來的,並且層級關系是緊跟著DOM的結構的。
下面這個例子演示了{{name}}表達式在不同的作用域下被解析成了不同的值
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>
<script>
function GreetCtrl($scope) {
$scope.name = 'World';
}
function ListCtrl($scope) {
$scope.names = ['Igor', 'Misko', 'Vojta'];
}
</script>
</head>
<body>
<div ng-controller="GreetCtrl">
Hello {{name}}!
</div>
<div ng-controller="ListCtrl">
<ol>
<li ng-repeat="name in names">{{name}}</li>
</ol>
</div>
</body>
</html>
在GreetCtrl控制器中的name等於'World'。在ListCtrl控制器中的name等於'Igor', 'Misko', 'Vojta'。因為它們的作用域不一樣。
控制器
視圖背後的控制代碼就是控制器。它的主要工作內容是構造模型和回調方法,並把模型和回調方法一起發送到視圖。 視圖可以看做是作用域在模板(HTML)上的投影。而作用域是一個中間地帶,它把模型整理好傳遞給視圖,把浏覽器事件傳遞給控制器。控制器和視圖的分離非常重要,因為:
(1)控制器是由Javascript寫的。Javascript是命令式的,命令式的語言適合用來編寫應用的行為。控制器不應該包含任何關於渲染代碼(DOM引用或者片段)。
(2)視圖模板是用HTML寫的。HTML是聲明是的,聲明式的語言適合用來編寫UI。視圖不應該包含任何行為。
(3)因為控制器和視圖沒有直接的調用關系,所以可以使多個視圖對應同一個控制器。這對“換膚(re-skinning)”、適配不同設備(比如移動設備和台式機)、測試,都非常重要。
模型
模型就是用來和模板結合生成視圖的數據。模型在作用域中可以被引用,這樣才能被渲染生成視圖。和其他框架不一樣的是,Angularjs對模型本身沒有任何限制和要求。你不需要繼承任何類也不需要實現指定的方法。 模型可以是哈希形式的原生對象,也可以是完整對象類型。簡而言之,模型可以是原生的Javascript對象。
視圖
所謂視圖,就是指用戶所看見的。 視圖的生命周期由作為一個模板開始,它將和模型合並,並最終渲染到浏覽器的DOM中。與其他模板系統不同的是,AngularJS使用一種獨特的形式來渲染視圖。
其他模板 - 大部分模板系統工作原理,都是一開始獲取一個帶有特殊標記的HTML形式字符串。通常情況下模板的特殊標記破壞了HTML的語法,以至於模板是不能用HTML編輯器編輯的。然後這個字符串會被送到模板引擎那裡解析,並和數據合並。合並的結果是一個可以被浏覽器解析的HTML字符串。這個字符串會被.innerHTML方法寫到DOM中。使用innerHTML會造成浏覽器的重新渲染。當模型改變時,這整個流程又要重復一遍。模板的生存周期就是DOM的更新周期。這裡我想強調是,這些模板的基礎是字符串。
AngularJS - AngularJS和其它模板系統不同。它使用的是DOM而不是字符串。模板仍然是用HTML字符串寫的,並且它仍然是HTML。浏覽器將它解析成DOM, 然後這個DOM會作為輸入傳遞給模板引擎,也就是我們的編譯器。編譯器查看其中的指令,找到的指令後,會開始監視指令內容中相應的模型。 這樣做,就使得視圖能“連續地”更新,不需要模板和數據的重新合並。你的模型也就成了你視圖變化的唯一原因。
指令
一個指令 就是一種“由某個屬性、元素名稱、css類名出現而導致的行為,或者說是DOM的變化”。指令能讓你以一種聲明式的方法來擴展HTML表示能力。
Filters過濾器
過濾器扮演著數據翻譯的角色。一般他們主要用在數據需要格式化為本地格式的時候。它參照了UNIX過濾的規則,並且也實現了“|”(管道)語法。
模塊和注入器
每個AngularJS應用都有一個唯一的注入器。注入器提供一個通過名字查找對象實例的方法。它將所有對象緩存在內部,所以如果重復調用同一名稱的對象,每次調用都會得到同一個實例。如果調用的對象不存在,那麼注入器就會讓實例的工廠(instance factory)函數創建一個新的實例。
一個模塊就是一種配置注入器的實例的工廠函數的方式,我們也稱它為“提供者(provider)”。
var myModule = angular.module('myModule', [])
myModule.factory('serviceA', function() { //定義serviceA的工廠函數,myModule模塊就是提供serviceA實例的工廠函數的提供者
return {
......
};
});
// create an injector and configure it from 'myModule'
var $injector = angular.injector('myModule');
// retrieve an object from the injector by name
var serviceA = $injector.get('serviceA'); //從注入器查找serviceA對象,這時注入器會讓實例serviceA的工廠函數factory創建一個新的實例serviceA返回
// always true because of instance cache
$injector.get('serviceA') === $injector.get('serviceA');
注入器真正強大之處在於讓方法和類型能夠通過注入器,請求到他們依賴的組件,而不需要自己加載依賴。
我們看看下面動態時間的這個例子:
<!doctype html>
<html ng-app="timeExampleModule">
<head>
<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>
<script">
angular.module('timeExampleModule', []).
factory('time', function($timeout) {
var time = {};
(function tick() {
time.now = new Date().toString();
$timeout(tick, 1000);
})();
return time;
});
function ClockCtrl($scope, time) {
$scope.time = time;
}
</script>
</head>
<body>
<div ng-controller="ClockCtrl">
Current time is: {{ time.now }}
</div>
</body>
</html>
你只要把需要的依賴寫在函數參數裡。當AngularJS調用這個函數時,它會自動填充好需要的參數。這個例子中,當ng-controller實例化構造器ClockCtrl的時候,它自動提供了指明的依賴time實例對象。
AngularJS 命名空間
為了防止意外的命名沖突, AngularJS為可能沖突的對象名加以前綴"$"。所以請不要在你自己的代碼裡用"$"做前綴,以免和AngularJS代碼發生沖突。
AngularJS權威教程 清晰PDF版 http://www.linuxidc.com/Linux/2015-01/111429.htm
希望你喜歡,並分享我的工作~帶你走近AngularJS系列:
如何在 AngularJS 中對控制器進行單元測試 http://www.linuxidc.com/Linux/2013-12/94166.htm
在 AngularJS 應用中通過 JSON 文件來設置狀態 http://www.linuxidc.com/Linux/2014-07/104083.htm
AngularJS 之 Factory vs Service vs Provider http://www.linuxidc.com/Linux/2014-05/101475.htm
AngularJS —— 使用 ngResource、RESTful APIs 和 Spring MVC 框架提交數據 http://www.linuxidc.com/Linux/2014-07/104402.htm
AngularJS 的詳細介紹:請點這裡
AngularJS 的下載地址:請點這裡