來源地址:http://blog.csdn.net/lastsweetop/article/details/51833370
Scopes簡介
Scopes是一個指向application模型的對象,是表達式執行的上下文,模擬application的DOM結構構成自己的層次結構。Scope可以觀察表達式和傳播事件。
Scopes特點
Scopes提供了API $watch觀察model的變化。
Scopes提供了API $apply通過系統把model的更改從“Angular realm”(controllers, services, Angular event handlers)外部傳播到view之中。
Scopes形成嵌套限制訪問application組件的屬性,同時也可以訪問一些共享model的屬性。所謂嵌套scopes不外乎兩種,一種child scopes,另一種isolate scope,child scope可以訪問從父scope繼承的屬性,isolate scope則不可以。
Scopes提供一個上下文,在其中的表達式可以求值。例如{ { username } }表達式是沒有意義的,但是看做是在scopes上下文中的表達式的求值的話就有意義了。
Scope作爲Data-Model
Scope是Controller和view之間的粘合劑,在程序鏈接階段directives在scopes中設置$watch表達式,$watch允許directives得到屬性改變的通知,並允許directives將改變後的值呈現給DOM視圖。
Controller和directives都和scopes相關聯,但是相互直接又不直接關聯。這樣使得Controller從指令和視圖中隔離開來。這一點很重要,這使得Controller和view相互不可知,極大的提高了程序的可測試性。
<!DOCTYPE html>
<html>
<head>
</head>
<script src="script/angular.min.js"></script>
<script>
angular.module('scopeExample', [])
.controller('MyController', ['$scope', function($scope) {
$scope.username = 'World';
$scope.sayHello = function() {
$scope.greeting = 'Hello ' + $scope.username + '!';
};
}]);
</script>
<body>
<div ng-app="scopeExample" ng-controller="MyController">
Your name:
<input type="text" ng-model="username">
<button ng-click='sayHello()'>greet</button>
<hr>
{{greeting}}
</div>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
Scope層次結構
每個angular的application都有一個root scope,但是可以有多個child scopes。 application可以有多個scopes,因爲一些directives可以創建新的child scopes,新的scopes一旦被創建,就會被作爲child scope增加到它的parent scope中,和他們鏈接的DOM一樣,構成一個樹形結構。 當Angular對{{name}}屬性求值時,先在相關聯的scope內尋找這個屬性,如果沒找到它就會去parent scope中找,一直搜到root scope。在JavaScript中這種行爲被稱爲prototypical inheritance,child scopes prototypically繼承於他們的parent scope。 這個例子講解了application中點scopes,還有屬性的prototypical inheritance,例子後面的一張圖描訴了scope的邊界。<!DOCTYPE html>
<html>
<head>
</head>
<script src="script/angular.min.js"></script>
<script>
angular.module('scopeExample', [])
.controller('GreetController', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.name = 'World';
$rootScope.department = 'Angular';
}])
.controller('ListController', ['$scope', function($scope) {
$scope.names = ['Igor', 'Misko', 'Vojta'];
}]);
</script>
<style>
.show-scope-demo.ng-scope,
.show-scope-demo .ng-scope {
border: 1px solid red;
margin: 3px;
}
</style>
<body ng-app="scopeExample">
<div class="show-scope-demo">
<div ng-controller="GreetController">
Hello {{name}}!
</div>
<div ng-controller="ListController">
<ol>
<li ng-repeat="name in names">{{name}} from {{department}}</li>
</ol>
</div>
</div>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
在DOM中檢索Scopes
Scope作爲一種數據屬性附加到DOM中,可以用來調試目的的檢索。root scope被附加到ng-app聲明的DOM節點。通常ng-app放在元素,但它可以放在其他元素,例如,只有部分視圖需要由Angular控制。 在調試器中檢查scope: 1.在瀏覽器中右鍵你感興趣的元素,並且選擇檢查元素(推薦chrome瀏覽器),您應該看到瀏覽器調試器與你點擊高亮顯示的元素。 2.調試器允許您訪問當前選中的元素在Console輸入$0 3.在Console中執行angular.element($0).scope()來檢索scopeScope事件傳播
Scopes也能傳播事件,就像傳播DOM事件一樣,可以通過broadcast廣播到子children scopes,通過emit擴散到parent scope<!DOCTYPE html>
<html ng-app="eventExample">
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<script>
angular.module('eventExample', [])
.controller('EventController', ['$scope', function($scope) {
$scope.count = 0;
$scope.$on('MyEvent', function() {
$scope.count++;
});
}]);
</script>
<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>
<button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>
<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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
Scope生命週期
瀏覽器接受事件的普通流程是這樣的,它會執行相應的javascript回調,一旦執行完畢瀏覽器將會重新呈現DOM並返回多個等待事件。 當瀏覽器調用進入javascript的代碼時,因爲這些代碼在Angular的執行上下文之外,意味着Angular根本無從得知model的更改。模型修改正確的執行流程是通過$apply方法進入到Angular的執行上下文。在$apply方法中僅僅模型改變將被Angular執行。例如一個監聽DOM事件的directive,比如ng-click,它必須在$apply方法中計算表達式的值。 在計算玩表達式的值之後,$apply會調用$digest方法。在$digest階段,scope會檢查所有的$watch表達式,並且比較它們和之前的值是否相同。這種髒檢查是異步的,這意味着比如$scope.username=”angular”這項任務不會立即引起$watch的更改,取而代之的是$watch通知被延遲到$digest階段。這種延遲是可取的,因爲它把多個模型的改變聚集成一個$watch通知,並且能保證同時$watch通知期間沒有其它$watches在運行。如果$watches改變了模型的值,它將被強制附加到$digest週期。 $apply()僞代碼function $apply(expr) {
try {
return $eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
$root.$digest();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Scopes和Directives
在編譯階段,編譯器將directives和DOM template進行匹配。directives通常分爲兩類: 1.觀察directives,比如雙大括號的表達式{{expression}},用 $watch()方法註冊監聽器。當表達式發生改變時,這種directives需要被通知,以便可以更新視圖。 2.監聽directives,比如ng-click,註冊一個監聽器在DOM上,當DOM的監聽器被激活,這個指令將執行相應的表達式,並且用$apply()方法更新視圖。 當一個外部事件 (如用戶操作,定時器或XHR)收到時,相關的表達式必須通過$apply()應用到scope中,以便所有的listeners被正確的更新。
創建Scopes的Directives
在大多數情況下,directives和scopes相互作用,但是不會創建一個新的scope實例。然而一些命令,比如ng-controller和ng-repeat,會創建新的child scopes並且附加child scopes到相應的DOM元素中。你可以用angular.element(aDomElement).scope()方法檢索任意DOM元素對應的scope。
Controllers和Scopes
Scopes和controllers直接的相互作用有以下幾種情況:
1.controllers使用Scopes來把controller的方法暴露給templates。
2.controllers定義可以改變model的方法和行爲。
3.controllers可以註冊watches到模型中,當controller行爲被執行後watches將立刻執行
Scope $watch的性能考慮
在Angular中,髒檢查scope是否有屬性改變是一項非常常規的操作。因此髒檢查函數必須是高效的,應該非常小心的注意到,髒檢查函數不能做任何DOM訪問,DOM訪問比JavaScript對象屬性訪問慢了好幾個數量級。
Scope $watch的深度
髒檢查可以使用三種策略:通過引用,通過集合內容,通過值。這三種髒檢查可以檢測到不同的變化,並且有不同的性能特徵。
1.通過引用(scope.$watch (watchExpression, listener))
檢測到一個值完全變爲一個新的值的變化,如果這個值是一個數組或者一個對象,它裏面的值的改變是不會檢測到的,這種策略是性能最高效的。
2.通過集合內容(scope.$watchCollection (watchExpression, listener))
可以檢測數據和對象內部的變化:當item被添加,刪除,重新排序。這種檢測深度是比較淺,因爲它不進入嵌套的集合。這種策略要比第一種更耗費性能,因爲他需要維護集合的副本內容。然而這種策略可以最小化必要的副本。
3.通過值(Watching by value (scope.$watch (watchExpression, listener, true))
可以檢測到一個任意嵌套結構的所有變化,這種檢測是最強力的,但是也是最耗性能的。每一次digest它都需要完整的遍歷整個數據結構,並且要保存一份完整的副本在內存中。
與瀏覽器的event loop集成
這張圖和下面的例子講解Angular和瀏覽器之間的事件循環。
1.瀏覽器的事件循環等待一個事件的到來。一個事件是一個用戶交互,計時器事件或網絡事件(從服務器響應)。
2.事件的回調被執行,然後進入JavaScript上下文,這個回調可以修改DOM結構。
3.一旦回調執行完畢,瀏覽器就會離開JavaScript上下文,基於DOM的變化重新渲染視圖。
Angular通過提供它自己的事件進程循環修改了普通的javascript流程。它吧javascript切割成兩部分:傳統部分和Angular執行上下文部分。在Angular執行上下文中僅僅一些操作可以受益:Angular數據綁定,異常處理,屬性檢測等等。你還可以調用$apply()從javascript執行上下文進入到Angular執行上下文。請記住一點,在大多數地方(controllers, services)已經被隱式的調用了。僅僅實現當自定義事件或者調用第三方庫的回調函數時,$apply()才需要被顯式的調用。
1.通過調用 scope.$apply(stimulusFn)進入到Angular執行上下文。stimulusFn是你希望在Angular上下文中執行的工作。
2.Angular執行stimulusFn(),它通常修改應用程序狀態。
3.Angular進入\$diges循環。這個循環由兩個小的循環構成:\$evalAsync隊列和 \$watch列表。\$digest循環不斷迭代,直到模型穩定。這意味着\$evalAsync隊列是空的,\$watch列表沒有任何變化。
4.\$evalAsync隊列常常用於調度當前堆棧框架之外的工作,並且該工作在瀏覽器視圖重新渲染之前。這個通常是用setTimeout(0)來做的,但是由於setTimeout(0)的緩慢,在每次事件後,瀏覽器重新加載視圖可能引起閃爍。
5.\$watch列表是一組在最近一次迭代可能發生變化的表達式的集合,如果變化被檢測到,那麼\$watch將會被調用,這個函數通常是用新的值去更新DOM。
6.一旦Angular的\$digest循環完成,將會有Angular上下文轉到Javascript上下文,接下來就是瀏覽器去重新呈現DOM以反應所有的改變。
下面講解一下hello world的示例,這個示例所做的是當用戶輸入字符到文本框中,如何實現了數據綁定。
1. 編譯階段
1.1 ng-model和input指令在<input>
控件設置了一個keydown監聽器。
1.2 interpolation設置一個$watch來獲得name改變的通知。
2. 運行時階段
2.1 在input控件中按下’X’鍵,瀏覽器會發出keydown事件。
2.2 input指令捕獲到了input值的改變,然後調用$apply(“name = ‘X’;”)去更新在Angular上下文的model。
2.3 Angular接受 name = ‘X’; 給model。
2.4 $digest循環開始
2.5 $watch列表檢測到了在name屬性上的變化然後通知interpolation從而更新DOM。
2.6.Angular退出執行上下文,接着退出keydown事件,退出javascript上下文。
2.7.瀏覽器用新的文本去重新呈現視圖。
<!DOCTYPE html>
<html lang="en" ng-app>
<head>
<script src="script/angular.min.js"></script>
</head>
<body>
Your name: <input type="text" ng-model="name" placeholder="World">
<hr>
Hello {{name || 'World'}}!
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11