AngularJS作用域是一個指向應用模型的對象。它是表達式的執行環境。作用域有層次結構,這個層次和相應的DOM幾乎是一樣的。作用域能監控表達式和傳遞事件。
{{username}}
本身是無意義的,除非把它放到指定username屬性的作用域中。作用域是控制器和視圖之間的“膠水”。在模板鏈接階段,指令設置好作用域的$watch表達式。$watch使得指令能知曉屬性的改變,這使得指令能重新渲染和更新DOM中的值。
控制器和指令都持有作用域的引用,但是不持有對方的引用。這使得控制器能從指令和DOM中脫離出來。這很重要,因為這使得控制器完全不需要知道view的存在,這大大改善了應用的測試。
舉個例子:
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/angular-1.0.2.min.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller="MyController"> Your name: <input type="text" ng-model="username"> <button ng-click='sayHello()'>greet</button> <hr> {{greeting}} </div> </body> </html>
script.js:
function MyController($scope) { $scope.username = 'World'; $scope.sayHello = function() { $scope.greeting = 'Hello ' + $scope.username + '!'; }; }
上例中MyController將值World賦給了作用域中的username。然後作用域將這個賦值的操作通知給input,然後input就會被渲染成預填充了值的樣子。這展示控制器如何將數據寫入到作用域。
同樣的,控制器能將行為添加到作用域,正如你看到的sayHello方法,這個方法是在用戶點擊'greet'按鈕時被調用的。
邏輯上來說,表達式{{greeting}}
的渲染需要:
{{greeting}}
DOM節點相關的作用域。在這個例子裡,就是傳入到MyController的作用域。你可以把作用域和它的屬性當做是用來渲染視圖的數據。作用域是視圖唯一相關聯的變化來源。
每一個AngularJS應用都有一個絕對的根作用域。但是可能有多個子作用域。
一個應用可以有多個作用域,因為有一些指令會生成新的子作用域(參考指令的文檔看看哪些指令會創建新作用域)。當新作用域被創建的時候,他們會被當成子作用域添加到父作用域下,這使得作用域會變成一個和相應DOM結構一個的樹狀結構。
當AngularJS執行表達式{{username}}
,它會首先查找和當前節點相關的作用域中叫做username的屬性。如果沒找到,那就會繼續向上層作用域搜索,直到根作用域。在Javascript中,這被稱為原型類型的繼承,子作用域以原型的形式繼承自父作用域。
下面這個例子展示了應用中的作用域,它們的繼承關系。
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/angular-1.0.2.min.js"></script> <script src="script.js"></script>
<style>
.doc-example-live .ng-scope {
border: 1px dashed red;
}
</style> </head> <body> <div ng-controller="EmployeeController"> Manager: {{employee.name}} [ {{department}} ]<br> Reports: <ul> <li ng-repeat="employee in employee.reports"> {{employee.name}} [ {{department}} ] </li> </ul> <hr> {{greeting}} </div> </body> </html>
script.js:
function EmployeeController($scope) { $scope.department = 'Engineering'; $scope.employee = { name: 'Joe the Manager', reports: [ {name: 'John Smith'}, {name: 'Mary Run'} ] }; }
注意當作用域和元素相關聯的時候,AngularJS會自動給相應元素添加ng-scope類名。這個例子中的作用域范圍突出顯示了。子作用域的存在是很有必要的,因為迭代器要執行{{employee.name}}
表達式,它會根據不同的作用域生成不同的值。同樣的,{{department}}
的執行是繼承自根作用域的,因為只有根作用域中定義了它。
作用域是作為$scope的數據屬性關聯到DOM上的,並且能在需要調試的時候被獲取到。根作用關聯的DOM就是ng-app指令定義的地方。一般來說ng-app都是放在<html>
元素中的,但是也能放在其他元素中。
在控制台中想獲取關聯的作用域:angular.element($0).scope()
作用域中的事件傳遞是和DOM事件傳遞類似的。事件可以廣播給子作用域或者傳遞給父作用域。舉個例子:
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/angular-1.0.2.min.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller="EventController"> Root scope <tt>MyEvent</tt> count: {{count}} <ul> <li ng-repeat="i in [1]" ng-controller="EventController"> <button ng-click="$emit('MyEvent')">$emit('MyEvent')</button> //當點擊此按鈕時,會觸發MyEvent事件,這時會把此事件也傳遞給父作用域,也就是Root scope,這時它的count會增加1.當然同級的Middle scope的count也會加1. <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button> //當點擊此按鈕時,會觸發MyEvent事件,這時會把此事件傳遞給子作用域,也就是Leaf scope,這時它的count會增加1,當然同級的Middle scope的count也會加1. <br> Middle scope <tt>MyEvent</tt> count: {{count}} <ul> <li ng-repeat="item in [1, 2]" ng-controller="EventController"> Leaf scope <tt>MyEvent</tt> count: {{count}} </li> </ul> </li> </ul> </div> </body> </html>
script.js:
function EventController($scope) { $scope.count = 0; $scope.$on('MyEvent', function() { //監聽MyEvent事件 $scope.count++; }); }
浏覽器接收到事件後的一般工作流程是執行一個相應的Javascript回調。回調一執行完,浏覽器就會重新渲染DOM並且重新回到等待事件的狀態。
當浏覽器調用AngularJS上下文之外的Javascript代碼時,AngularJS是不知道模型的更改的。要正確處理模型的更改,就要使用$apply方法進入AngularJS的執行上下文。只有在$apply方法內執行的模型修改才會正確地被AngularJS處理。比如,一個指令監聽DOM事件,比如ng-click
,它必須在$apply方法中來執行表達式。
執行完表達式之後,$apply會進入$digest階段。在$digest階段,作用域會檢查所有的$watch表達式,並將它們和之前的值比較。這意味著賦值語句,如$scope.username="angular"
不會馬上導致$watch被通知,取而代之的是它會等到$digest階段才被通知。這種方式是合理的,因為它將多個模型的更新整合到一個$watch通知裡,並且保證了一個$watch通知期間不會有其他同樣的$watch執行。
在模板編譯階段,編譯器在DOM中匹配指令。指令通常分為兩種:
{{expression}}
,會用$watch來注冊一個監聽者。無論表達式什麼時候改變,這類型的指令都會被通知,並且能更新視圖。ng-click
,會向DOM注冊一個監聽者。當DOM監聽者觸發,指令會執行相關的表達式並且使用$apply方法更新視圖。當一個外界事件(比如用戶操作,計時器或者XHR)觸發時,相應的表達式必須在$apply()方法內,並由其相應的作用域調用,這樣所有的監聽者才會被正確地更新。
大部分情況下,指令和作用域交互,不會產生新的作用域實例。但是,有些指令,比如ng-controller
和ng-repeat
會創建新的作用域,並關聯到相應的DOM元素上,你可以使用angular.element(aDomElement).scope()
方法來獲得某一個DOM元素相關的作用域。
作用域和控制器在以下幾種情況下交互:
ng-controller
)檢測屬性的改變是AngularJS中一項常用的操作,所以它應該是高效的。要注意的是,執行檢測的方法不應該包含任何DOM操作,因為在Javascript對象中,DOM獲取要比屬性獲取慢很多很多。
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 的下載地址:請點這裡