AngularJS–Scope(做用域)

點擊查看AngularJS系列目錄
轉載請註明出處:http://www.cnblogs.com/leosx/css


 

Scope

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 + '!';
  };
}]);

 

效果圖:

image

在上面的例子當中,MyController 控制器的 username 屬性的值是World 。它被ng-model指令分配到了input 對象上,進行了一個雙向綁定,也就是說,當用戶在UI界面中,在input中輸入數據時,會自動把數據更新到username 屬性上,若是在js中,修改username 屬性的值,那麼一樣的,Angular會通知UI進行更新input的顯示值。這就是雙向綁定。

 

Scope的繼承

每個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;
}

 

效果圖:

image

image

請注意:當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的事件廣播

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++;
  });
}]);

 

效果圖:

image

 

點擊這裏查看示例

 

Scope的生命週期

正常狀況下,瀏覽器接收到事件觸發信息後,會直接執行回調函數。一旦回調完成,瀏覽器從新渲染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的所使用的內存。

 

scope和指令

在編譯階段,編譯器會去匹配對DOM模板上的指令。這些指令一般分爲如下兩類:

一、監視類(Observing)指令,例如:雙花括號{{表達式}},它使用$watch() 方法去進行監視。這類表達式在表達式變化了的時候,會通知UI進行界面更新。

二、監聽類(Listener)指令,例如ng-click指令,註冊一個對DOM的監聽,當DOM事件觸發時,會去執行它本身的表達式,而且使用$apply() 方法去更新UI界面。

當接收到一個外部事件(例如用戶動做,定時器或XHR),它們的表達式必須在$apply()方法中去執行,以便全部監視者能正確更新數據到所本身所在的scope上。

 

指令建立Scope

在大多數狀況下,directives (指令)和scope會相互影響,可是不會建立出新的scope出來。然而,有一些指令,諸如:ng-controllerng-repeat指令, 它們會建立一個child scope而後附加到對應的DOM元素上去。你能夠調用angular.element(aDomElement).scope() 方法去取到任何DOM元素身上的scope信息。

 

控制器(controller)和scope

控制器和scope會在如下幾種狀況下相互影響:

一、控制器(controller)使用scope來暴露方法和屬性給模板(template)使用和訪問。

二、controller定義的一些方法(或者行爲behavior),能夠去改變scope上的屬性值。

三、控制器能夠爲model註冊watche 監視,這些監視會在controller的動做加載以後當即啓動。

查看更多和ng-controller相關的信息,點擊這裏

 

Scope $watch 的性能注意事項

在Angular中,scope對model屬性的變動檢查是一個公共的方法。正因如此,變動檢查功能必須是有效的。應該注意的是,變動檢查並無去作任何的DOM操做的哦!訪問DOM元素會要比訪問JavaScript的屬性的速度要慢。

 

scope $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一個副本到內存中去。因此,它的性能就得你本身評估是否適當了。建議仍是不要深度太大,層級太多,否則性能不好了。

圖示以下:

concepts-scope-watch-strategies (1)

 

和瀏覽器的事件循環的集成

下面的圖表和例子描述了瀏覽器的事件循環如何和Angular相互做用的。

一、瀏覽器的事件循環等待事件的到來。一個事件通常是用戶的交互觸發,定時器事件,或網絡事件。

二、事件的回調將會在事件觸發時,進入該事件的JavaScript環境進行執行。回調函數能夠修改DOM的結構。

三、一旦回調執行,瀏覽器會離開JavaScript環境,而且會從新渲染修改後DOM到UI界面。

圖片描述:

concepts-runtime

 

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。

相關文章
相關標籤/搜索