angular學習(五)—— Scopes

來源地址: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
在上面的例子中,MyController首先將World賦值給scope中的username屬性,scope將通知input這次賦值,然後將username的值預先填入到輸入框中,這裏演示了controller怎麼樣將數據寫入到scope中。 同樣controller可以分配行爲給scope,比如這個例子中的sayHello方法,當用戶點擊greet按鈕時,sayHello方法就會被調用。sayHello方法會去讀取username屬性並創建一個greeting屬性。這裏演示的是綁定到input部件的屬性的自動更新。 greeting屬性的呈現邏輯上分爲兩步: 1.在DOM中的模板裏定義了{{greeting}},首先檢索DOM關聯的scope。 2.針對以上檢索到的scope,計算求值greeting,並將結果分配給這個enclosing DOM元素的文本。 你可以把scopes及其屬性是用於呈現view的數據,而且是view相關的單一真實數據來源。

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
![這裏寫圖片描述](https://img-blog.csdn.net/20161125170052951) ![這裏寫圖片描述](https://img-blog.csdn.net/20161125170115787) 注意到Angular自動將scope影響到的元素附上了ng-scope的class樣式。style定義了在新的scope創建的地方都會紅色高亮顯示。{{name}}在不同的scope內會得出不同的值,{{department}} prototypically繼承於root scope。

在DOM中檢索Scopes

Scope作爲一種數據屬性附加到DOM中,可以用來調試目的的檢索。root scope被附加到ng-app聲明的DOM節點。通常ng-app放在元素,但它可以放在其他元素,例如,只有部分視圖需要由Angular控制。 在調試器中檢查scope: 1.在瀏覽器中右鍵你感興趣的元素,並且選擇檢查元素(推薦chrome瀏覽器),您應該看到瀏覽器調試器與你點擊高亮顯示的元素。 2.調試器允許您訪問當前選中的元素在Console輸入$0 3.在Console中執行angular.element($0).scope()來檢索scope

Scope事件傳播

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
![這裏寫圖片描述](https://img-blog.csdn.net/20161129110045993)

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
Scope週期概要: 1.創建 在Angular程序的引導階段,root scope通過$injector被創建。在template鏈接階段,一些directives創建新的child scopes。 2.Watcher註冊 在template鏈接階段,directives註冊Watcher到scope。這些watches被用於將模型的值傳播到DOM中。 3.Model變化 爲了讓變化正確的被觀察到,你應該僅僅使用scope.$apply()。Angular APIs會隱含的做這些,所以不需要額外的調用$apply()在controllers中,或者一些異步工作中比如 $http, $timeout or $interval 服務。 4.觀察變化 在$apply的末尾,Angular會在root scope內調用$digest,會在所有的child scopes傳播,在$digest週期,所有的$watched的表達式或者函數將被檢查模型的改變,如果改變發生,watch調5.Scopechildscopechildscope調scope.destroy()銷燬它們。這將阻止$digest傳播到改child scope,並且該child scope佔用的內存也會被垃圾回收器回收。

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章