AngularJS做用域是一個指向應用模型的對象。它是表達式的執行環境。做用域有層次結構,這個層次和相應的DOM幾乎是同樣的。做用域能監控表達式和傳遞事件。html
{{username}}
自己是無心義的,除非把它放到指定username屬性的做用域中。做用域是控制器和視圖之間的「膠水」。在模板連接階段,指令設置好做用域的$watch表達式。$watch使得指令能知曉屬性的改變,這使得指令能從新渲染和更新DOM中的值。angularjs
控制器和指令都持有做用域的引用,可是不持有對方的引用。這使得控制器能從指令和DOM中脫離出來。這很重要,由於這使得控制器徹底不須要知道view的存在,這大大改善了應用的測試。express
舉個例子:瀏覽器
<!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:app
function MyController($scope) { $scope.username = 'World'; $scope.sayHello = function() { $scope.greeting = 'Hello ' + $scope.username + '!'; }; }
上例中MyController將值World賦給了做用域中的username。而後做用域將這個賦值的操做通知給input,而後input就會被渲染成預填充了值的樣子。這展現控制器如何將數據寫入到做用域。函數
一樣的,控制器能將行爲添加到做用域,正如你看到的sayHello方法,這個方法是在用戶點擊'greet'按鈕時被調用的。測試
邏輯上來講,表達式{{greeting}}
的渲染須要:spa
{{greeting}}
DOM節點相關的做用域。在這個例子裏,就是傳入到MyController的做用域。你能夠把做用域和它的屬性當作是用來渲染視圖的數據。做用域是視圖惟一相關聯的變化來源。調試
每個AngularJS應用都有一個絕對的根做用域。可是可能有多個子做用域。code
一個應用能夠有多個做用域,由於有一些指令會生成新的子做用域(參考指令的文檔看看哪些指令會建立新做用域)。當新做用域被建立的時候,他們會被當成子做用域添加到父做用域下,這使得做用域會變成一個和相應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獲取要比屬性獲取慢不少不少。
加油!