點擊查看AngularJS系列目錄
轉載請註明出處:http://www.cnblogs.com/leosx/css
Scope 是一個應用程序的模塊的對象。它是表達式的執行上下文。它充斥在DOM樹的各個層級上。做用域Scope能夠監控表達式也能夠廣播事件(監控表達式,就是WPF中的屬性變動通知,至關有做用喲!)。html
Scope的特色angularjs
Scope有一個監控方法($watch),用它來監視model(模型)的變化,也就是上面所說的監視並作變動通知。chrome
Scope有一個($apply)方法,咱們用它就能夠去執行一些來自非Angular的代碼,或者第三方內庫功能。它的好處就是讓第三方函數加入了Angular框架,走了AngularJS的生命週期,咱們就能夠在AngularJS的生命中期中詳細的控制了。api
Scope也能夠嵌套到應用程序限制訪問的一些組件中去。而且能夠提供一些共享的屬性。嵌套進去的Scope是一個「子Scope」或者是一個「獨立Scope」。須要注意的是:「子Scope」是繼承自它的父級Scope的,它有父級Scope的屬性和方法。而「獨立Scope」則沒有繼承父級Scope。要查看更多獨立Scope(isolated scopes),請點擊連接進行查看。Scope爲咱們的表達式提供了上下文。例如單純的{{username}}表達式是沒有任何意義的,由於它沒有上下文,訪問不到username變量。爲了使得表達式起做用,咱們就要在表達式對應的組件中的Scope上定義一個username屬性,而且爲它賦值,而後這個表達式就有上下文了,就能夠訪問到uername屬性而且渲染顯示。數組
Scope之VM(ViewModel)瀏覽器
Scope是應用程序(app)的controller和view之間的粘合劑。在模板(template)進行連接(linking)期間,會使用scope的$watch
表達式去監視一些指令所引用的對象,換句話說就是$watch
能夠對Scope上的model(ViewModel)進行監控,當model上的屬性變化時,就會通知UI進行更新,若是咱們自定義了監視。那麼一樣會調用咱們自定義的監視代碼。這個就是WPF上的屬性變動通知。是至關有用的東東。來一個例子:網絡
第一個文件:index.htmlapp
<div ng-controller="MyController"> Your name: <input type="text" ng-model="username"> <button ng-click='sayHello()'>greet</button> <hr> {{greeting}} </div>
第二個文件script.js框架
angular.module('scopeExample', []) .controller('MyController', ['$scope', function($scope) { $scope.username = 'World'; $scope.sayHello = function() { $scope.greeting = 'Hello ' + $scope.username + '!'; }; }]);
效果圖:
在上面的例子當中,MyController
控制器的 username
屬性的值是World
。它被ng-model
指令分配到了input
對象上,進行了一個雙向綁定,也就是說,當用戶在UI界面中,在input中輸入數據時,會自動把數據更新到username
屬性上,若是在js中,修改username
屬性的值,那麼一樣的,Angular會通知UI進行更新input的顯示值。這就是雙向綁定。
每個AngularJS應用程序有且只有一個根scope(root scope),可是,容許擁有不少個子集scope。
在一個Angular應用程序中,是能夠擁有多個scope的,由於有一些指令,是會自動建立scope的(參照指導文件,以查看哪些指令建立新的scope)。當指令自動建立scope的時候,會繼承父級scope。也就是擁有上級scope的全部屬性和方法。
例如,咱們在執行 {{name}}
表達式的時候,首先會在scope中尋找這個name屬性,若是找不到,那麼會自動去父級scope上找name屬性,以此類推,直到rootScope爲止。
下面這個例子演示了scope的應用,也是一個原型繼承(prototypical inheritance)。例子中,明確標識出了scope的邊界。
第一個文件:index.html
<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>
第二個文件:script.js
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']; }]);
第三個文件:style.css
.show-scope-demo.ng-scope, .show-scope-demo .ng-scope { border: 1px solid red; margin: 3px; }
效果圖:
請注意:當scope被附加到了元素上以後,會自動的爲這個元素加上ng-scope
樣式。這個例子中的<style>
樣式用來標識scope邊界。
在DOM樹上檢索scope
scope被附加到DOM上的$scope屬性上(在應用程序內,是不能夠這樣去檢索scope的哦!)。其中rootscope會被附加到ng-app
指令所對應的DOM上。一般,ng-app
指令會被附加到<html>
元素上去。也能夠被附加到其餘的標籤上去。
讓咱們來使用chrome的debugger來介紹下scope。
一、在chrome瀏覽器中,右擊你要查看的元素,在右鍵菜單中選擇【審查元素】,而後你就能夠看見這個元素被debugger高亮顯示出來了。
二、調試器容許咱們在控制檯中使用
$0
變量去訪問當前選中的元素。三、能夠在控制檯使用
angular.element($0).scope()
或者$scope
去訪問選中元素所在的scope
。
scope能夠以相似DOM事件的樣子進行廣播一個事件。該事件能夠被廣播到子集scope或者父級scope上去。咱們來看一個例子:
文件一:index.html
<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>
文件二:script.js
angular.module('eventExample', []) .controller('EventController', ['$scope', function($scope) { $scope.count = 0; $scope.$on('MyEvent', function() { $scope.count++; }); }]);
效果圖:
正常狀況下,瀏覽器接收到事件觸發信息後,會直接執行回調函數。一旦回調完成,瀏覽器從新渲染DOM並顯示出來,也就是更新UI,而且馬上返回,以便等待接收其它的事件觸發。
當瀏覽器在AngularJS的scope(上下文)以外調用JavaScript的時候,AngularJS是沒法知道model被修改了,也就沒法實現雙向綁定了。要想正確的實現model的雙向綁定,那麼就要把JavaScript使用$apply
方法加入到AngularJS的生命週期當中去,這樣AngularJS以外的JS也能夠正確的進行雙向綁定了。例如,若是一個指令,監聽DOM事件,好比:ng-click
它也是在$apply
方法中進行調用的,這樣才能正確的響應數據模型(VM-ViewModel)。實現雙向綁定。
在表達式計算完畢後,$apply
方法會調用$digest
。在執行$digest
方法的時候,scope會檢查全部的$watch
表達式;並將它們與之前的值進行比較。注意:這個值的變動檢查是異步方式進行的。這意味着,若是執行了諸如:$scope.username="angular"
的操做將不會當即更新UI;由於$watch
的通知尚未發出來。$watch
的變動通知會被延遲到$digest
執行的時候進行。這個延遲是合理的哈!由於它組合了model的多個屬性變動通知到一個$watch
變動通知表單中。這樣就能夠保證在$watch
的通知表單在執行通知的時候,沒有其它的通知也在同時進行。
Scope的生命週期有以下幾個階段:
一、建立階段 -- 讓AngularJS應用程序在啓動$injector的期間,就會建立 root scope (惟一的根Scope)。當模板(template)在進行連接的階段,一些指令會建立子scope(child scope)。
二、監視器的註冊 -- 在template(模板)連接期間,指令會經過scope的
$watch
註冊監視。這些監視用於將變動廣播到DOM上,以進行UI變動。三、model變化 -- 要想model的變化被正確的監視到,你必須把更改model的表達式放在scope.$apply()方法中執行。Angular在這方面作的很精簡的,在controller中執行model修改或者在諸如$http, $timeout 或者 $interval等異步服務中修改model的話,都會自動去調用
$apply
方法的,就再也不用本身去調用$apply
方法了。四、觀察變化 -- 在
$apply
方法結束的時候,Angular會調用rootscope上的$digest方法進入銷燬階段,而後再廣播給child scope,告訴他們執行$digest方法,進入他們的銷燬階段。在銷燬階段,全部使用$watch
進行監視的model的表達式或者屬性還有方法都會被檢查是否有變動,若是有變動,那麼久會執行通知。五、scope的銷燬 -- 當一個child scope不在須要的時候,那麼你就能夠調用scope.$destroy() 方法去銷燬它們。這個動做將會中止$digest銷燬廣播的向下傳遞,而且也容許內存去回收child scope的所使用的內存。
在編譯階段,編譯器會去匹配對DOM模板上的指令。這些指令一般分爲如下兩類:
一、監視類(Observing)指令,例如:雙花括號
{{表達式}}
,它使用$watch() 方法去進行監視。這類表達式在表達式變化了的時候,會通知UI進行界面更新。二、監聽類(Listener)指令,例如ng-click指令,註冊一個對DOM的監聽,當DOM事件觸發時,會去執行它本身的表達式,而且使用$apply() 方法去更新UI界面。
當接收到一個外部事件(例如用戶動做,定時器或XHR),它們的表達式必須在$apply()方法中去執行,以便全部監視者能正確更新數據到所本身所在的scope上。
在大多數狀況下,directives (指令)和scope會相互影響,可是不會建立出新的scope出來。然而,有一些指令,諸如:ng-controller和 ng-repeat指令, 它們會建立一個child scope而後附加到對應的DOM元素上去。你能夠調用angular.element(aDomElement).scope()
方法去取到任何DOM元素身上的scope信息。
控制器和scope會在如下幾種狀況下相互影響:
一、控制器(controller)使用scope來暴露方法和屬性給模板(template)使用和訪問。
二、controller定義的一些方法(或者行爲behavior),能夠去改變scope上的屬性值。
三、控制器能夠爲model註冊watche 監視,這些監視會在controller的動做加載以後當即啓動。
查看更多和ng-controller相關的信息,點擊這裏。
$watch
的性能注意事項在Angular中,scope對model屬性的變動檢查是一個公共的方法。正因如此,變動檢查功能必須是有效的。應該注意的是,變動檢查並無去作任何的DOM操做的哦!訪問DOM元素會要比訪問JavaScript的屬性的速度要慢。
$watch
的深度變動檢查能夠用三種策略來實現:經過引用(reference)、經過集合(collection contents)、經過value。這幾種方式的性能是有不一樣的;並且方式也不同。
一、經過引用方式(by reference):也就是
scope.$watch(watchExpression, listener)
方法。當檢測到變化時,$watch表達式監控的全部值會更新,而且總體返回。需要注意的是,若是咱們監視的是一個Array數組或者一個對象時,對象或數組裏面的數據變化時,是不會被檢測到的。這個策略的性能是最好的。二、經過集合(collection contents)方式:也就是
scope.$watchCollection(watchExpression, listener)
方法;這個方法就彌補了上面一種方式的不足,這種方式會監視到數組或者對象的內部變化。當爲一個數組增長,刪除或者從新排序時,都會進行變動通知的。不過這種方式並非嵌套監視的,它只監視被監視集合或者對象下的直接子元素的變化,不會監視子元素的子元素。相比引用方式去監聽,這種方式性能上確定會差一些。可是,某些狀況下,使用它是最好的選擇。三、經過value方式:也就是
scope.$watch (watchExpression, listener, true)
方法來進行監視,這種方式會監視到被監視對象的全部子元素,不管是間接子元素仍是直接子元素,都會被監視。他是監視最全面的,同時也是性能代價最大的。在銷燬階段,它會遍歷嵌套的全部數據,而且會copy一個副本到內存中去。因此,它的性能就得你本身評估是否適當了。建議仍是不要深度太大,層級太多,否則性能不好了。
圖示以下:
下面的圖表和例子描述了瀏覽器的事件循環如何和Angular相互做用的。
一、瀏覽器的事件循環等待事件的到來。一個事件通常是用戶的交互觸發,定時器事件,或網絡事件。
二、事件的回調將會在事件觸發時,進入該事件的JavaScript環境進行執行。回調函數能夠修改DOM的結構。
三、一旦回調執行,瀏覽器會離開JavaScript環境,而且會從新渲染修改後DOM到UI界面。
圖片描述:
Angular經過提供一個屬於本身的事件輪詢處理機制去修改了正常的JavaScript流的執行。因此JavaScript的執行環境就分爲了正常的JavaScript流環境和Angular事件環境兩種狀況。只有那些在Angular執行上下文環境中執行的操做,纔會具備Angular提供的諸如:數據綁定,異常處理(exception handling),屬性監視等功能。你也可使用$apply()
方法把常規的JavaScript代碼加入到Angularjs的執行上下文環境中進行執行,這樣咱們的JavaScript代碼就能夠具備上面提到的那些Angular提供的功能了。請記住,在大多數地方諸如:controllers, services等指令中,當事件處理完成後,都會自動爲你調用$apply()
方法。也就是你不用本身手動調用$apply()
方法了。 只有咱們自定義的JavaScript代碼,或者本身直接操做DOM的JavaScript代碼,或者第三方類庫的回調函數纔會須要手動調用$apply()
方法來加入Angular的執行上下文環境中。
$apply()
方法的使用以及執行流程大體有以下幾步:
一、經過調用
scope.$apply(stimulusFn)
方法進入Angular的執行上下文環境,其中stimulusFn
是你但願在Angular中執行的工做.二、Angular會執行
stimulusFn()
方法,一般這種工做都會修改應用程序的狀態。三、Angular進入輪詢($digest loop)。這個輪詢中會有兩個小的輪詢,它們分別是進行$evalAsync隊列處理,和執行和$watch 監視相關的工做。$digest 輪詢會一直迭代,直到模型(model)保持穩定,也就意味着$evalAsync隊列是空的了,而且$watch 監視表單中再也不有任何改變。
四、$evalAsync隊列用於調度那些不在當前堆棧(我理解爲Angular環境)中工做,而且要再瀏覽器進行渲染以前的工做。好比 ,一般
setTimeout(0)
的調用完成了,可是受到延遲的影響或者那些可能由於在事件執行後,瀏覽器從新渲染View的時候形成的畫面(UI)閃爍的問題的影響的工做,就會被$evalAsync隊列調度。五、$watch 的集合是一組在最後一次迭代的時候可能發生了變化的表達式。若是檢測到了變化,
$watch
方法就會被調用,這個方法一般是把DOM上對應元素的舊值更新爲如今變化後的新值(這時改變了DOM,瀏覽器並無從新渲染)。六、一旦$digest 輪詢完成了,便會離開Angular的執行上下文。在這以後,瀏覽器就開始了從新渲染DOM,也就是刷新UI界面了。
下面闡釋了當用戶在文本框中輸入一段Hello world
文字後,是如何實現數據的綁定效果的。
1、在編譯階段:
一、ng-model 指令和input directive 指令會監聽
<input>
控件的keydown
事件。二、插值(interpolation)使用$watch 去註冊
name
的變動時的通知。
2、在運行階段:
一、在鍵盤上按下'
X
' 鍵,使得瀏覽器去激活這個<input>
控件的keydown
事件;二、input (點擊我,查看有哪些input指令)指令捕獲到了輸入值的改變,而且調用了$apply
("name = 'X';")
方法去更新Angular執行上下文的模型(model);三、Angular把model上的
name = 'X';
四、開始$digest輪詢;
五、$watch 的集合監視到了
name
屬性的改變,而且通知了interpolation,從而更新了DOM;六、Angular退出執行上下文,這樣就會退出
keydown
事件所在的JavaScript的執行上下文;七、瀏覽器從新渲染view視圖,刷新UI。