一、什麼是Scope?
scope[http://code.angularjs.org/1.0.2/docs/api/ng. rootScope.Scope]是一個指向應用model的object。它也是expression(http://www.cnblogs.com/lcllao/archive/2012/09/16/2687162.html)的執行上下文。scope被放置于一個類似應用的DOM結構的層次結構中。scope可以監測(watch, watch)expression和傳播事件。
二、scope的特性
scope提供 watchAPI(http://code.angularjs.org/1.0.2/docs/api/ng. rootScope.Scope# watch),用于監測model的變化。scope提供 apply API(http://code.angularjs.org/1.0.2/docs/api/ng.rootScope.Scope#apply),在“Angular realm”(controller、server、angular event handler)之外,從系統到視圖傳播任何model的變化。
scope可以在提供到被共享的model屬性的通路的時候,被嵌入到獨立的應用元件中。scope通過(原型),從parent scope中繼承屬性。
scope在expression求值之時提供上下文環境。例如,{{username}}表達式是無意義的,除非它與一個特定的定義了”username”屬性的scope一起進行求值。
三、Scope as Data-Model(scope作為資料模型)
scope是在應用controller與view之間的紐帶。在模版linking[http://www.cnblogs.com/lcllao/archive/2012/09/04/2669802.html]的階段,directive[http://www.cnblogs.com/lcllao/archive/2012/09/09/2677190.html]在scope中設定 watch表達式。 watch讓directive能夠得知屬性的變化,使得directive将更新後的值渲染到DOM中。
controller和directive兩者都與scope有引用,但它們兩者之間沒有(引用)(Both controllers and directives have reference to the scope, but not to each other)。這樣的安排,将controller從directive和DOM中隔離開來。這是一個重要的地方,因為它讓controller與view是隔離的,極大地提升了應用的可測試性(greatly improves the testing story of the applications)。
<!DOCTYPE HTML>
<html lang="zh-cn" ng-app>
<head>
<meta charset="UTF-8">
<title>data-model</title>
<style type="text/css">
.ng-cloak {
display: none;
}
</style>
</head>
<body class="ng-cloak">
<div ng-controller="MyController">
你的名字: <input type="text" ng-model="username"/>
<button ng-click="sayHello()">歡迎</button>
<hr/>
{{greeting}}
</div>
<script src="../angular-1.0.1.js" type="text/javascript"></script>
<script type="text/javascript">
function MyController($scope) {
$scope.username = "My Little Dada";
$scope.sayHello = function() {
$scope.greeting = "Hello~" + $scope.username + "!";
};
}
</script>
</body>
</html>
在上面的例子中我們可以注意到MyController以”My Little Dada”對scope中的username屬性進行指派。然後,scope通知input進行指派,将username的值預先填入input中。這展示了controller如何做才能夠寫入資料到scope中。
相似地,controller可以将行為附加在scope中,正如那個當使用者點選“歡迎”按鈕時觸發的sayHello方法一樣。sayHello方法可以讀取username屬性,也可以建立greeting屬性。這表明,當它們綁定到HTML input控件時,scope中的屬性會自動更新。
邏輯上,顯示{{greeting}}涉及以下兩點:
與定義了{{greeting}}表達式的模版DOM節點一起檢索scope。在這個例子中,這個scope與傳遞到MyController中的scope是相同的。(我們在稍後将會讨論scope的層次結構)
通過之前檢索的scope,對greeting表達式進行求值,然後将結果作為封閉DOM元素的text的值。
我們可以認為,scope和它自己的屬性可以作為資料,用于渲染視圖。scope是所有和view相關的東西單一的真相來源(The scope is the single source-of-truth for all things view related)。
從可測試性來看,controller和view的分離是值得欣喜的,因為它允許我們在沒有渲染細節的幹擾下(專注于)測試行為。
it('should say hello', function() {
var scopeMock = {};
var cntl = new MyController(scopeMock);
// Assert that username is pre-filled
expect(scopeMock.username).toEqual('World');
// Assert that we read new username and greet
scopeMock.username = 'angular';
scopeMock.sayHello();
expect(scopeMock.greeting).toEqual('Hello angular!');
});
四、Scope Hierarchies(scope層次結構)
每一個angular應用有且隻有一個root scope,但可以擁有多個child scope。
應用可以擁有多個child scope,因為一些directive會建立新的child scope(參考directive文檔,檢視哪些directive可建立新的scope,如ng-repeat)。當新的scope被建立後,他們将作為一個child scope,加入到parent scope中。這樣,建立了一個與它們附屬的DOM相似的樹結構。
當angular對{{username}}求值時,它首先檢視與目前元素關聯的scope的username屬性。如果沒有找到對應的屬性,它将會一直向上搜尋parent scope,直到到達root scope。在javascript中,這個行為被稱為“原型繼承”,child scope典型地繼承自它們的parent。這個例子說明應用中的scope(是怎樣的),屬性的原型繼承。
<!DOCTYPE HTML>
<html lang="zh-cn" ng-app>
<head>
<meta charset="UTF-8">
<title>scope-hierarchies</title>
<style type="text/css">
.ng-cloak {
display: none;
}
.ng-scope {
border: px dashed red;
}
</style>
</head>
<body class="ng-cloak">
<div ng-controller="MyController">
經理:{{employee.name}} [{{department}}] <br/>
報告:
<ul>
<li ng-repeat="employee in employee.reports">
{{employee.name}} [{{department}}]
</li>
</ul>
<hr/>
{{greeting}}
</div>
<script src="../angular-1.0.1.js" type="text/javascript"></script>
<script type="text/javascript">
function MyController($scope) {
$scope.department = "某部";
$scope.employee = {
name:"My Little Dada",
reports: [
{name:"Lcllao"},
{name:"那個誰^o^"}
]
};
}
</script>
</body>
</html>
注意,angular自動放置ng-scope class到與scope粘附的元素中。
五、Retrieving Scopes from the DOM(從DOM中檢索scope)
scope作為$scope資料屬性附加到DOM中,可以被用于以調試作為目的的檢索。(在應用中通過這個方式檢索Scope是不可能的。)附加到的DOM的root scope的位置是通過ng-app directive的位置定義的。通常ng-app是放置在元素中,但它也可以放置在其他元素中,例如,隻有一部分視圖需要被angular控制。
在debugger中檢視scope:
- 在浏覽器中,對着感興趣的元素點選右鍵,選擇“檢視元素”。我們可以看到浏覽器debugger高亮了我們選中的元素。
- debugger允許我們在console中通過$0變量去通路目前選擇的元素。
- 想檢視關聯的scope,我們可以在console中輸入:angular.element($0).scope()
六、Scope Events Propagation(Scope事件傳播)
scope可以以類似于DOM事件的方式進行事件傳播。事件可以被broadcast(http://code.angularjs.org/1.0.2/docs/api/ng.rootScope.Scope#broadcast)到child scope或者emit(http://code.angularjs.org/1.0.2/docs/api/ng.rootScope.Scope#emit)到parent scope中。(目前scope如果有監聽,也會執行)
<!DOCTYPE HTML>
<html lang="zh-cn" ng-app>
<head>
<meta charset="UTF-8">
<title>scope-event-propagation</title>
<style type="text/css">
.ng-cloak {
display: none;
}
</style>
</head>
<body class="ng-cloak">
<div ng-controller="MyController">
root scope count:{{count}}
<ul>
<li ng-repeat="i in [1]" ng-controller="MyController">
<button ng-click="$emit('MyEvent')">$emit("MyEvent")</button>
<button ng-click="$broadcast('MyEvent')">$broadcast("MyEvent")</button>
<br/>
middle scope count:{{count}}
<ul>
<li ng-repeat="item in [1,2]" ng-controller="MyController">
Leaf scope count:{{count}}
</li>
</ul>
</li>
</ul>
</div>
<script src="../angular-1.0.1.js" type="text/javascript"></script>
<script type="text/javascript">
function MyController($scope) {
$scope.count = ;
$scope.$on("MyEvent", function() {
$scope.count++;
});
}
</script>
</body>
</html>
七、Scope Life Cycle(scope生命周期)
浏覽器正常的事件流中,當浏覽器接收到事件後,它會執行一個相應的javascript回調。一旦回調函數執行完畢後,浏覽器将會重繪DOM,并傳回到繼續等待事件的狀态。
當浏覽器在angular執行環境外調用javascript代碼時,這意味着angular是不知道model的改變的。要正确處理model的修改,這個指令必須通過使 apply方法進入angular執行環境。隻有在 apply方法中的model變更,才會正确地被angular統計。例如,一個directive監聽了DOM事件,例如ng-click,它必須在$apply方法中對表達式進行求值。
在對表達式求值之後, apply方法執行一個 digest。在 digest階段裡,scope檢查所有 watch監聽的表達式,将現在的值與舊的值作比較。髒檢查(dirty checking)是異步的。這意味着指派語句(例如 scope.username=”angular”)将不會馬上導緻一個 watch被通知,反而, watch的通知将會延遲到 digest階段。這個延遲是必須的,因為它把多個model更新聯合到一個 watch通知中,這保證了在 watch通知的過程中,沒有其他 watch在執行。如果一個 watch改變了model的值,那麼它将會強制增加一個 digest周期。1)Creation(建立scope)rootscope是在應用啟動的過程中, injector(http://code.angularjs.org/1.0.2/docs/api/AUTO. injector)建立的。在模版linking的過程中,一些directive會建立新的childscope。2)Watcherregistration(注冊watcher)在模版linking過程中,directive在scope中注冊 watch。這些watch将會被用作向DOM傳播model的值。
3) Model mutation(Model變化)
為了讓變化被正确地檢測,我們需要将他們包裹在scope. apply中。(angularAPI已經隐式地做了這部操作,是以,當在controller中做同步的工作或者與 http或者 timeout一起做異步工作的時候,不需要額外的 apply調用)。
4) Mutation observation(變化監測)
在 apply的結尾,angular會在rootscope執行一個 digest周期,這将會傳播到所有child scope中。在 digest周期中,所有注冊了 watch的表達式或者function都會被檢查,判斷model是否發生了改變,如果改變發生了,那麼對應的 watch監聽器将會被調用。5)Scopedestruction(scope銷毀)當childscope不再是必須的時候,childscope的産生者有責任通過scope. destroy() API銷毀它們(child scope)。這将會停止 digest的調用傳播傳播到childscope中,讓被childscopemodel使用的記憶體可以被gc(garbagecollector)回收。1.ScopesandDirectives在編譯階段中,compiler依靠DOM模版比對directive。directive通常可以分為兩大類:觀察型directive(Observingdirectives),例如dobule−curly表達式expression,使用 watch方法注冊監聽器。無論什麼時候,表達式(的值)發生改變,這類directive必須被通知,進而更新view。
監聽型directive(Listener directive),例如ng-click,注冊一個監聽器到DOM中。當DOM的監聽器觸發時,directive會執行相關的表達式,并通過使用 apply方法更新視圖。當一個外部的事件(例如使用者動作、timer或者XHR)被監聽到,相關的expression必須通過 apply方法應用到scope中,讓所有監聽器能夠正确地更新。
2. Directives that Create Scopes
在大多數的情況中,directive和scope是互相影響的,但不會建立新的scope執行個體。然而,一些directive(例如ng-controller和ng-repeat)會建立新scope,附加child scope到對應的DOM元素中。我們通過使用angular.element(aDomElement).scope()檢視任意DOM元素的scope。
-
Controllers and Scopes
在以下的情況中,scope與controller是互相影響的:
controller使用scope暴露controller方法到模版中(檢視ng-controller(http://code.angularjs.org/1.0.2/docs/api/ng.directive:ngController))。
controller定義方法(行為),可以改變model(scope上的屬性)。
controller可能在model中注冊watch。這些watch會在controller行為執行之後馬上執行。
-
Scope watchPerformanceConsiderations(Scope watch的性能考慮)
在angular中,為了檢測屬性的變化而對scope進行髒檢測(Dirty checking),是一個普遍的操作。為此,這要求dirty checking函數必須是高效的。應小心dirty checking函數不要做任何DOM通路操作,因為DOM通路的速度比通路javascript對象屬性的速度要慢好幾個數量級。