概覽
這篇文檔描述了AngularJS的主要組成部分,以及它們?nèi)绾螀f(xié)同工作,。它們是:
formatDate
啟動
下面解釋了我們是如何把這一切運轉(zhuǎn)起來的(用一張圖和一個例子來解釋):
- 瀏覽器載入HTML,然后把它解析成DOM,。
- 瀏覽器載入angular.js腳本,。
- AngularJS等到
DOMContentLoaded 事件觸發(fā)。
- AngularJS尋找
ng-app 指令,,這個指令指示了應(yīng)用的邊界,。
- 使用
ng-app 中指定的模塊來配置注入器($injector)。
- 注入器($injector)是用來創(chuàng)建“編譯服務(wù)($compile service)”和“根作用域($rootScope)”的,。
- 編譯服務(wù)($compile service)是用來編譯DOM并把它鏈接到根作用域($rootScope)的,。
ng-init 指令將“World”賦給作用域里的name這個變量。
- 通過
{{name}} 的替換,,整個表達式變成了“Hello World”,。
index.html:
<!doctype html>
<html ng-app>
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
</head>
<body>
<p ng-init=" name='World' ">Hello {{name}}!</p>
</body>
</html>
運行效果
執(zhí)行期
下面的圖和例子解釋了AngularJS如何和瀏覽器的事件回路(event loop)交互。
- 瀏覽器的事件循環(huán)等待事件的觸發(fā),。所謂事件包括用戶的交互操作,、定時事件、或者網(wǎng)絡(luò)事件(服務(wù)器的響應(yīng)),。
- 事件觸發(fā)后,,回調(diào)會被執(zhí)行。此時會進入Javascript上下文,。通?;卣{(diào)可以用來修改DOM結(jié)構(gòu),。
- 一旦回調(diào)執(zhí)行完畢,瀏覽器就會離開Javascript上下文,,并且根據(jù)DOM的修改重新渲染視圖,。
AngularJS通過使用自己的事件處理循環(huán),改變了傳統(tǒng)的Javascript工作流,。這使得Javascript的執(zhí)行被分成原始部分和擁有AngularJS執(zhí)行上下文的部分,。只有在AngularJS執(zhí)行上下文中運行的操作,才能享受到AngularJS提供的數(shù)據(jù)綁定,,異常處理,,資源管理等功能和服務(wù)。你可以使用 $apply() 來從普通Javascript上下文進入AngularJS執(zhí)行上下文,。記住,,大部分情況下(如在控制器,服務(wù)中),,$apply都已經(jīng)被用來處理當(dāng)前事件的相應(yīng)指令執(zhí)行過了,。只有當(dāng)你使用自定義的事件回調(diào)或者是使用第三方類庫的回調(diào)時,才需要自己執(zhí)行$apply ,。
- 通過調(diào)用
scope.$apply(stimulusFn) 來進入AngularJS的執(zhí)行上下文,,這里的stimulusFn是你希望在AngularJS執(zhí)行上下文中執(zhí)行的函數(shù)。
- AngularJS會執(zhí)行
stimulusFn() ,,這個函數(shù)一般會改變應(yīng)用的狀態(tài),。
- AngularJS進入$digest循環(huán)。這個循環(huán)是由兩個小循環(huán)組成的,,這兩個小循環(huán)用來處理處理$evalAsync隊列和$watch列表,。這個$digest循環(huán)直到模型“穩(wěn)定”前會一直迭代。這個穩(wěn)定具體指的是$evalAsync對表為空,,并且$watch列表中檢測不到任何改變了,。
- 這個$evalAsync隊列是用來管理那些“視圖渲染前需要在當(dāng)前棧框架外執(zhí)行的操作的”,。這通常使用
setTimeout(0) 來完成的,。用setTimeout(0) 會有速度慢的問題。并且,,因為瀏覽器是根據(jù)事件隊列按順序渲染視圖的,,還會造成視圖的抖動。
- $watch列表是一個表達式的集合,,這些表達式可能是自上次迭代后發(fā)生了改變的,。如果有檢測到了有改變,那么$watch函數(shù)就會被調(diào)用,它通常會把新的值更新到DOM中,。
- 一旦AngularJS的$digest循環(huán)結(jié)束,,整個執(zhí)行就會離開AngularJS和Javascript的上下文。這些都是在瀏覽器為數(shù)據(jù)改變而進行重渲染之后進行的,。
下面解釋了"hello world"的例子是怎么樣實現(xiàn)“將用戶輸入綁定到視圖上”的效果,。
- 在編譯階段:
- input上的
ng-model 指令給<input> 輸入框綁定了keydown事件;
{{name}} 這個變量替換表單式建立了一個 $watch 來接受 name 變量改變的通知,。
- 在執(zhí)行期階段:
- 按下任何一個鍵(以X鍵為例),,都會觸發(fā)一個 input 輸入框的keydown事件;
- input 上的指令捕捉到 input 里值得改變,,然后調(diào)用 $apply("name = 'X';")來更新處于AngularJS執(zhí)行上下文中的模型,;
- AngularJS將
name='X' 應(yīng)用到模型上;
- $digest 循環(huán)開始,;
- $watch 列表檢測到了name值的變化,,然后通知
{{name}} 變量替換的表達式,這個表達式負責(zé)將DOM進行更新,;
- AngularJS退出執(zhí)行上下文,,然后退出Javascript上下文中的keydown事件;
- 瀏覽器以更新的文本重渲染視圖,。
index.html:
<!doctype html>
<html ng-app>
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
</head>
<body>
<input ng-model="name">
<p>Hello {{name}}!</p>
</body>
</html>
運行效果
作用域(Scope)
作用域是用來檢測模型的改變和為表達式提供執(zhí)行上下文的,。它是分層組織起來的,并且層級關(guān)系是緊跟著DOM的結(jié)構(gòu)的,。(請參考單個指令的文檔,,有一些指令會導(dǎo)致新的作用域的創(chuàng)建。)
下面這個例子演示了{{name}} 表達式在不同的作用域下解析成不同的值,。這個例子下面的圖片顯示作用域的邊界,。
index.html:
<!doctype html>
<html ng-app>
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
<script src="script.js"></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>
style.css:
.show-scope .doc-example-live.ng-scope,
.show-scope .doc-example-live .ng-scope {
border: 1px solid red;
margin: 3px;
}
script.js:
function GreetCtrl($scope) {
$scope.name = 'World';
}
function ListCtrl($scope) {
$scope.names = ['Igor', 'Misko', 'Vojta'];
}
運行效果
控制器
視圖背后的控制代碼就是控制器。它的主要工作內(nèi)容是構(gòu)造模型,,并把模型和回調(diào)方法一起發(fā)送到視圖。 視圖可以看做是作用域在模板(HTML)上的“投影(projection)”,。而作用域是一個中間地帶,,它把模型整理好傳遞給視圖,把瀏覽器事件傳遞給控制器,??刂破骱湍P偷姆蛛x非常重要,因為:
- 控制器是由Javascript寫的,。Javascript是命令式的,,命令式的語言適合用來編寫應(yīng)用的行為。控制器不應(yīng)該包含任何關(guān)于渲染代碼(DOM引用或者片段),。
- 視圖模板是用HTML寫的,。HTML是聲明是的,聲明式的語言適合用來編寫UI,。視圖不應(yīng)該包含任何行為,。
- 因為控制器和視圖沒有直接的調(diào)用關(guān)系,所以可以使多個視圖對應(yīng)同一個控制器,。這對“換膚(re-skinning)”,、適配不同設(shè)備(比如移動設(shè)備和臺式機)、測試,,都非常重要,。
index.html:
<!doctype html>
<html ng-app>
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
Hello {{name}}!
<button ng-click="action()">
OK
</button>
</div>
</body>
</html>
script.js:
function MyCtrl($scope) {
$scope.action = function() {
$scope.name = 'OK';
}
$scope.name = 'World';
}
運行效果
模型
模型就是用來和模板結(jié)合生成視圖的數(shù)據(jù)。模型必須在作用域中時可以被引用,,這樣才能被渲染生成視圖,。和其他框架不一樣的是,Angularjs對模型本身沒有任何限制和要求,。你不需要繼承任何類也不需要實現(xiàn)指定的方法以供調(diào)用或者改變模型,。 模型可以是原生的對象哈希形式的,也可以是完整對象類型的,。簡而言之,,模型可以是原生的Javascript對象。
視圖
所謂視圖,,就是指用戶所看見的,。 視圖的生命周期由作為一個模板開始,它將和模型合并并最終渲染到瀏覽器的DOM中,。與其他模板系統(tǒng)不同的是,,AngularJS使用一種獨特的形式來渲染視圖。
- 其他模板 - 大部分模板系統(tǒng)工作原理,,都是一開始獲取一個帶有特殊標記的HTML形式字符串,。通常情況下模板的特殊標記破換了HTML的語法,以至于模板是不能用HTML編輯器編輯的,。然后這個字符串會被送到模板引擎那里解析,,并和數(shù)據(jù)合并。合并的結(jié)果是一個可以被瀏覽器解析的HTML字符串,。這個字符串會被
.innerHTML 方法寫到DOM中,。使用innerHTML會造成瀏覽器的重新渲染。當(dāng)模型改變時,,這整個流程又要重復(fù)一遍,。模板的生存周期就是DOM的更新周期,。這里我想強調(diào)是,這些模板系統(tǒng)模板的基礎(chǔ)是字符串,。
- AngularJS - AngularJS和其它模板系統(tǒng)不同,。它使用的是DOM而不是字符串。模板仍然是用HTML字符串寫的,,并且它仍然是HTML,。瀏覽器將它解析成DOM, 然后這個DOM會作為輸入傳遞給模板引擎,,也就是我們的編譯器,。編譯器查看其中的指令,找到的指令后,,會開始監(jiān)視指令內(nèi)容中相應(yīng)的模型,。 這樣做,就使得視圖能“連續(xù)地”更新,,不需要模板和數(shù)據(jù)的重新合并,。你的模型也就成了你視圖變化的唯一動因。
index.html:
<!doctype html>
<html ng-app>
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
</head>
<body>
<div ng-init="list = ['Chrome', 'Safari', 'Firefox', 'IE'] ">
<input ng-model="list" ng-list> <br>
<input ng-model="list" ng-list> <br>
<pre>list={{list}}</pre> <br>
<ol>
<li ng-repeat="item in list">
{{item}}
</li>
</ol>
</div>
</body>
</html>
運行效果
指令
一個指令 就是一種“由某個屬性,、元素名稱,、css類名出現(xiàn)而導(dǎo)致的行為,或者說是DOM的變化”,。指令能讓你以一種聲明式的方法來擴展HTML表示能力,。下面演示了一個增加了數(shù)據(jù)綁定的“內(nèi)容可編輯”HTML。
index.html:
<!doctype html>
<html ng-app="directive">
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div contentEditable="true" ng-model="content">Edit Me</div>
<pre>model = {{content}}</pre>
</body>
</html>
script.js:
<!doctype html>
<html ng-app="directive">
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div contentEditable="true" ng-model="content">Edit Me</div>
<pre>model = {{content}}</pre>
</body>
</html>
style.css:
div[contentEditable] {
cursor: pointer;
background-color: #D0D0D0;
margin-bottom: 1em;
padding: 1em;
}
運行效果
Filters過濾器
過濾器扮演著數(shù)據(jù)翻譯的角色,。一般他們主要用在數(shù)據(jù)需要格式化成本地格式的時候,。它參照了UNIX過濾的規(guī)則,并且也實現(xiàn)了“|”(管道)語法,。
index.html:
<!doctype html>
<html ng-app>
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
</head>
<body>
<div ng-init="list = ['Chrome', 'Safari', 'Firefox', 'IE'] ">
Number formatting: {{ 1234567890 | number }} <br>
array filtering <input ng-model="predicate">
{{ list | filter:predicate | json }}
</div>
</body>
</html>
運行效果
模塊和注入器
每個AngularJS應(yīng)用都有一個唯一的注入器,。注入器提供一個通過名字查找對象實例的方法。它將所有對象緩存在內(nèi)部,,所以如果重復(fù)調(diào)用同一名稱的對象,,每次調(diào)用都會得到同一個實例。如果調(diào)用的對象不存在,,那么注入器就會讓實例工廠(instance factory)創(chuàng)建一個新的實例,。
一個模塊就是一種配置注入器實例工廠的方式,我們也稱為“提供者(provider)”,。
// Create a module
var myModule = angular.module('myModule', [])
// Configure the injector
myModule.factory('serviceA', function() {
return {
// instead of {}, put your object creation here
};
});
// 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');
// always true because of instance cache
$injector.get('serviceA') === $injector.get('serviceA');
注入器真正強大之處在于它可以用來調(diào)用方法和實例化的類型。這個精妙的特性讓方法和類型能夠通過注入器請求到他們依賴的組件,,而不需要自己加載依賴,。
// You write functions such as this one.
function doSomething(serviceA, serviceB) {
// do something here.
}
// Angular provides the injector for your application
var $injector = ...;
///////////////////////////////////////////////
// the old-school way of getting dependencies.
var serviceA = $injector.get('serviceA');
var serviceB = $injector.get('serviceB');
// now call the function
doSomething(serviceA, serviceB);
///////////////////////////////////////////////
// the cool way of getting dependencies.
// the $injector will supply the arguments to the function automatically
$injector.invoke(doSomething); // This is how the framework calls your functions
注意,你唯一需要寫的就是上例中指出的函數(shù),并把需要的依賴寫在函數(shù)參數(shù)里,。當(dāng)AngularJS調(diào)用這個函數(shù)時,,它會自動體充好需要的參數(shù)。
看看下面的動態(tài)時間的例子,,注意它是如何在構(gòu)造器中列舉出依賴的,。當(dāng)ng-controller 實例化構(gòu)造器的時候,它自動提供了指明的依賴,。沒有必要去創(chuàng)建依賴,、查找依賴、或者提供一個依賴的引用給注入器,。
index.html:
<!doctype html>
<html ng-app="timeExampleModule">
<head>
<script src="http://code./angular-1.1.0.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="ClockCtrl">
Current time is: {{ time.now }}
</div>
</body>
</html>
script.js:
angular.module('timeExampleModule', []).
// Declare new object call time,
// which will be available for injection
factory('time', function($timeout) {
var time = {};
(function tick() {
time.now = new Date().toString();
$timeout(tick, 1000);
})();
return time;
});
// Notice that you can simply ask for time
// and it will be provided. No need to look for it.
function ClockCtrl($scope, time) {
$scope.time = time;
}
運行效果
AngularJS 命名空間
為了防止意外的命名沖突,, AngularJS為可能沖突的對象名加以前綴"$"。所以請不要在你自己的代碼里用"$"做前綴,,以免和AngularJS代碼發(fā)生沖突,。
|