歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> AngularJS開發指南9:AngularJS作用域的詳解

AngularJS開發指南9:AngularJS作用域的詳解

日期:2017/3/1 9:34:00   编辑:Linux編程

AngularJS作用域是一個指向應用模型的對象。它是表達式的執行環境。作用域有層次結構,這個層次和相應的DOM幾乎是一樣的。作用域能監控表達式和傳遞事件。

作用域的特點

  • 作用域提供APIs($watch)來觀察模型的變化。
  • 作用域提供APIs($apply)將任何模型的改變,反映到視圖上。
  • 作用域能通過共享模型成員的方式嵌套到應用組件上。一個作用域從父作用域繼承屬性。
  • 作用域提供表達式執行的上下文。比如說表達式{{username}}本身是無意義的,除非把它放到指定username屬性的作用域中。

作為數據模型的作用域

作用域是控制器和視圖之間的“膠水”。在模板鏈接階段,指令設置好作用域的$watch表達式。$watch使得指令能知曉屬性的改變,這使得指令能重新渲染和更新DOM中的值。

控制器和指令都持有作用域的引用,但是不持有對方的引用。這使得控制器能從指令和DOM中脫離出來。這很重要,因為這使得控制器完全不需要知道view的存在,這大大改善了應用的測試。

舉個例子:

<!doctype html>
<html ng-app>
  <head>
    <script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
    <script src="script.js"></script>
  </head>
  <body>
    <div ng-controller="MyController">
      Your name:
        <input type="text" ng-model="username">
        <button ng-click='sayHello()'>greet</button>
      <hr>
      {{greeting}}
    </div>
  </body>
</html>

script.js:

function MyController($scope) {
  $scope.username = 'World';

  $scope.sayHello = function() {
    $scope.greeting = 'Hello ' + $scope.username + '!';
  };
}

上例中MyController將值World賦給了作用域中的username。然後作用域將這個賦值的操作通知給input,然後input就會被渲染成預填充了值的樣子。這展示控制器如何將數據寫入到作用域。

同樣的,控制器能將行為添加到作用域,正如你看到的sayHello方法,這個方法是在用戶點擊'greet'按鈕時被調用的。

邏輯上來說,表達式{{greeting}}的渲染需要:

  • 獲取模板中定義了{{greeting}}DOM節點相關的作用域。在這個例子裡,就是傳入到MyController的作用域。
  • 結合上一步獲取到的作用域來計算表達式的值,將該值在DOM中替換掉表達式。

你可以把作用域和它的屬性當做是用來渲染視圖的數據。作用域是視圖唯一相關聯的變化來源。

作用域層級

每一個AngularJS應用都有一個絕對的根作用域。但是可能有多個子作用域。

一個應用可以有多個作用域,因為有一些指令會生成新的子作用域(參考指令的文檔看看哪些指令會創建新作用域)。當新作用域被創建的時候,他們會被當成子作用域添加到父作用域下,這使得作用域會變成一個和相應DOM結構一個的樹狀結構。

當AngularJS執行表達式{{username}},它會首先查找和當前節點相關的作用域中叫做username的屬性。如果沒找到,那就會繼續向上層作用域搜索,直到根作用域。在Javascript中,這被稱為原型類型的繼承,子作用域以原型的形式繼承自父作用域。

下面這個例子展示了應用中的作用域,它們的繼承關系。

<!doctype html>
<html ng-app>
  <head>
    <script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
    <script src="script.js"></script>
  <style>
    .doc-example-live .ng-scope {
        border: 1px dashed red;
    }
  </style>
  </head>
  <body>
    <div ng-controller="EmployeeController">
      Manager: {{employee.name}} [ {{department}} ]<br>
      Reports:
        <ul>
          <li ng-repeat="employee in employee.reports">
            {{employee.name}} [ {{department}} ]
          </li>
        </ul>
      <hr>
      {{greeting}}
    </div>
  </body>
</html>

script.js:

function EmployeeController($scope) {
  $scope.department = 'Engineering';
  $scope.employee = {
    name: 'Joe the Manager',
    reports: [
      {name: 'John Smith'},
      {name: 'Mary Run'}
    ]
  };
}

注意當作用域和元素相關聯的時候,AngularJS會自動給相應元素添加ng-scope類名。這個例子中的作用域范圍突出顯示了。子作用域的存在是很有必要的,因為迭代器要執行{{employee.name}}表達式,它會根據不同的作用域生成不同的值。同樣的,{{department}}的執行是繼承自根作用域的,因為只有根作用域中定義了它。

從DOM中獲取作用域

作用域是作為$scope的數據屬性關聯到DOM上的,並且能在需要調試的時候被獲取到。根作用關聯的DOM就是ng-app指令定義的地方。一般來說ng-app都是放在<html>元素中的,但是也能放在其他元素中。

在控制台中想獲取關聯的作用域:angular.element($0).scope()

作用域事件的傳遞

作用域中的事件傳遞是和DOM事件傳遞類似的。事件可以廣播給子作用域或者傳遞給父作用域。舉個例子:

<!doctype html>
<html ng-app>
  <head>
    <script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
    <script src="script.js"></script>
  </head>
  <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>    //當點擊此按鈕時,會觸發MyEvent事件,這時會把此事件也傳遞給父作用域,也就是Root scope,這時它的count會增加1.當然同級的Middle scope的count也會加1.
          <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>  //當點擊此按鈕時,會觸發MyEvent事件,這時會把此事件傳遞給子作用域,也就是Leaf scope,這時它的count會增加1,當然同級的Middle scope的count也會加1.
          <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>

script.js:

function EventController($scope) {
  $scope.count = 0;
  $scope.$on('MyEvent', function() {  //監聽MyEvent事件
    $scope.count++;
  });
}

作用域的聲明周期

浏覽器接收到事件後的一般工作流程是執行一個相應的Javascript回調。回調一執行完,浏覽器就會重新渲染DOM並且重新回到等待事件的狀態。

當浏覽器調用AngularJS上下文之外的Javascript代碼時,AngularJS是不知道模型的更改的。要正確處理模型的更改,就要使用$apply方法進入AngularJS的執行上下文。只有在$apply方法內執行的模型修改才會正確地被AngularJS處理。比如,一個指令監聽DOM事件,比如ng-click,它必須在$apply方法中來執行表達式。

執行完表達式之後,$apply會進入$digest階段。在$digest階段,作用域會檢查所有的$watch表達式,並將它們和之前的值比較。這意味著賦值語句,如$scope.username="angular"不會馬上導致$watch被通知,取而代之的是它會等到$digest階段才被通知。這種方式是合理的,因為它將多個模型的更新整合到一個$watch通知裡,並且保證了一個$watch通知期間不會有其他同樣的$watch執行。

  1. 創建——根作用域是在應用被$injector啟動時創建的。在模板鏈接階段,有些指令會創建新的子作用域。
  2. 觀察者注冊——在模板鏈接階段,指令會在作用域上注冊觀察者。這些���察者是用來將模型的改變傳遞給DOM的。
  3. 模型變化——為了正確地觀測到模型變化,你需要並且只能在scope.$apply()中改變他們。(AngularJS的API會隱式地這麼做,所以在控制器或者在$http,$timeout等服務中你不需要額外的調用$apply)。
  4. 變化的觀測——在$apply的最後,AngularJS會在根作用域中執行一個$digest循環,它會將變化傳遞給所有子作用域。在$digest循環中,所有的$watch表達式或者函數都會被檢測,來觀察模型的變化。如果有變化被檢測到了,$watch的監聽回調就會被調用。
  5. 作用域的銷毀——如果子作用域不再有用了。那麼子作用域的創建者就會負責用scope.$destroy() API來將它銷毀。這會停止$digest再調用此子作用域,並且讓此作用域占用的內容能夠被回收。

在模板編譯階段,編譯器在DOM中匹配指令。指令通常分為兩種:

  • 觀察型的指令,例如雙花括號表達式{{expression}},會用$watch來注冊一個監聽者。無論表達式什麼時候改變,這類型的指令都會被通知,並且能更新視圖。
  • 監聽者型的指令,比如ng-click,會向DOM注冊一個監聽者。當DOM監聽者觸發,指令會執行相關的表達式並且使用$apply方法更新視圖。

當一個外界事件(比如用戶操作,計時器或者XHR)觸發時,相應的表達式必須在$apply()方法內,並由其相應的作用域調用,這樣所有的監聽者才會被正確地更新。

大部分情況下,指令和作用域交互,不會產生新的作用域實例。但是,有些指令,比如ng-controllerng-repeat會創建新的作用域,並關聯到相應的DOM元素上,你可以使用angular.element(aDomElement).scope()方法來獲得某一個DOM元素相關的作用域。

作用域和控制器在以下幾種情況下交互:

  • 控制器通過作用域來向模板暴露方法(參考ng-controller
  • 控制器定義裡能改變模型(作用域的屬性)的方法(行為)
  • 控制器在模型上注冊了觀察者。這些觀察者會在控制器行為執行後立即被執行

檢測屬性的改變是AngularJS中一項常用的操作,所以它應該是高效的。要注意的是,執行檢測的方法不應該包含任何DOM操作,因為在Javascript對象中,DOM獲取要比屬性獲取慢很多很多。

AngularJS權威教程 清晰PDF版 http://www.linuxidc.com/Linux/2015-01/111429.htm

希望你喜歡,並分享我的工作~帶你走近AngularJS系列

  1. 帶你走近AngularJS - 基本功能介紹 http://www.linuxidc.com/Linux/2014-05/102140.htm
  2. 帶你走近AngularJS - 體驗指令實例 http://www.linuxidc.com/Linux/2014-05/102141.htm
  3. 帶你走近AngularJS - 創建自定義指令 http://www.linuxidc.com/Linux/2014-05/102142.htm

如何在 AngularJS 中對控制器進行單元測試 http://www.linuxidc.com/Linux/2013-12/94166.htm

在 AngularJS 應用中通過 JSON 文件來設置狀態 http://www.linuxidc.com/Linux/2014-07/104083.htm

AngularJS 之 Factory vs Service vs Provider http://www.linuxidc.com/Linux/2014-05/101475.htm

AngularJS —— 使用 ngResource、RESTful APIs 和 Spring MVC 框架提交數據 http://www.linuxidc.com/Linux/2014-07/104402.htm

AngularJS 的詳細介紹:請點這裡
AngularJS 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved