1. 程式人生 > >前端開發框架總結之Angular實用技巧(二)

前端開發框架總結之Angular實用技巧(二)

                                前端開發框架總結之Angular實用技巧(二)

上文講了Angular自定義指令,今天我們再來介紹下Angular資料繫結相關的知識。很多都是個人理解,大家選擇性參考吧。

  • 資料繫結、監聽、頁面重新整理

想要理解Angular資料雙向繫結原理,那就要去看ng-model指令的實現過程。在實際專案中我們要理解兩個重要變數$viewValue,$modelValue和兩個個重要方法$render,$setViewValue。其中$viewValue是與頁面相關的變數(和頁面值不一定相等),$modelValue是與資料模型相關的變數(不一定相等),$render方法是把$viewValue的值同步到頁面元素上,利用頁面元素事件的監聽會觸發$setViewValue方法把頁面的值同步到$viewValue,$modelValue,然後是同步到ctrl中的scope的屬性。在自定義指令中我們可以使用require:‘?^ngModel’的方式獲取到指令所在元素的ngModelCtrl物件。還有元素屬性獲取,自定義校驗,頁面重新整理方法重寫等方式在下面的例子中貼出。

一個頁面載入大致過程如下(只寫一些我關注的階段):

HTML頁面載入-----> HTML中的DOM元素載入-----> js載入------->根據頁面內容生成相應的watcher------>controler載入----->記錄controler中的watcher------->自定義指令link方法----->$scope的$apply方法------>scope的$digest方法----->遍歷所有watcher--->-------->根據規則,對所有watcher的回撥函式呼叫。------->ngModelwatch中會對$scope中的變數,$modelValue,$viewValue進行同步,然後呼叫ngModel的render方法,把值同步到頁面。(其他型別的繫結比如ng-bind,{{}},他們的同步方式類似,只是在最後一步的watcher回撥中走了不同的listener。)。

對於雙向繫結還有把頁面元素的值同步到$scope變數中的過程。這個是由頁面元素的時間觸發的,比如input的輸入等,這個會觸發ngModel的$setViewValue方法。把頁面的值通過給viewValue,modelValue,進而同步給$scope中的變數。

為了監聽一些$scope變數的變化,我們可以使用$watch方法,注意:如果監聽的變數是一個物件,那麼我們只改變物件的某個屬性是不會觸發watch方法的,因為物件的引用並沒有發生變化,如果想要觸發watch,就要在新增watch的時候指明需要深度比較。另外watch方法想要觸發,一定是變數值改變之後,觸發了digest資料同步,才會觸發watch方法的。

最後來講一下,主動觸發把$scope值同步到頁面的幾種方式。這裡會用到4個函式。$apply(),$digest(),$applyAsync(),$evalAsync();

$apply:遍歷所有的scope的watcher,把他們的資料同步至頁面。本質上最終呼叫的還是$digest。

$digest:遍歷當前scope中的watcher,把他們的資料同步至頁面。

$applyAsync:跟apply功能相同。只不過是他的“非同步方法”,把發同步的過程延遲觸發。在http請求頻繁的頁面可以使用$httpProvider.useApplyAsync(true).這樣可以把頁面重新整理合併,不會頻繁的觸發apply方法。

$evalAsync:跟apply的功能也類似,只不過如果一次apply觸發的遍歷如果還沒有結束的話,使用$evalAsync會把這次要更新的內容在本次遍歷中進行執行,而不是等上一個apply迴圈執行完成之後再發起一遍遍歷了。

真正理解了以上4中方法的意思,就可以在合適的場景中使用合適的方法,進而提高頁面效率。

另外比如ng-click等指令、包括$http方法的回撥完成後,框架中都是會主動呼叫digest或者apply的,所以一般不需要我們手動觸發,但如果元素的click事件是我們手動繫結的,那麼就是不會觸發同步操作的。$timeout方法也會觸發digest過程的。

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
  <title>angular-test</title>
</head>
<body ng-app="ExampleApp">

  <div ng-controller="testCtrl">
      <input ng-model="inputValue" test-render name="testInput">
      <button id="btn" >手動繫結的點選事件</button>
      <button ng-click="change()" >指令繫結的點選事件</button>
  </div>

  <script src="jquery-3.2.1.min.js"></script>
  <script src="angular.js"></script>

  <script>
      var app = angular.module('ExampleApp', []);

      app.controller('testCtrl', function ($scope,$timeout) {
          $scope.inputValue = '初值';
          $scope.$watch('inputValue',function (newVal, oldVal) {
              console.log("watch:inputValue,newVal:" + newVal + ",oldVal:" + oldVal );
          });

          $scope.$watch('obj',function (newVal, oldVal) {
              console.log("watch:obj,newVal:" + newVal + ",oldVal:" + oldVal );
          },true);

          $scope.obj = new Object();

          $('#btn').on('click',btnClick);

          $scope.change = function () {
              $scope.obj.name = 'test';
              $scope.inputValue = '修改後的input值';
          }

          function btnClick() {
              $scope.obj.name = 'test';
              $scope.inputValue = '修改後的input值';

              //$timeout方法是不需要呼叫apply的,它自己會主動觸發。
              /*$timeout(function () {
                  $scope.obj.name = 'test';
                  $scope.inputValue = '修改後的input值';
              });*/

              //$scope.$apply();
              //$scope.$applyAsync();
              //$scope.$evalAsync();
              $scope.$digest();

          }
      });

      app.directive('testRender',function () {
          return {
              restrict: 'A',
              require:'?^ngModel',
              link:function (scope,element,attrs,ngModel) {
                  //獲取地址應屬性所在元素的屬性
                  console.log('獲取屬性值name:' + attrs.name);
                  console.log('獲取屬性值ngModel:' + scope.$eval(attrs.ngModel));

                  //可以主動設定viewValue,但是必須手動呼叫render方法重新整理介面。
                  //ngModel.$setViewValue('主動設定的input');

                  //可以重寫render方法,對render過程進行攔截。
                  /*ngModel.$render = function () {
                      console.log("這是我的重新整理邏輯");
                  };*/

                  //手動觸發的重新整理。
                  //ngModel.$render();
              }
          }
      });
  </script>
</body>
</html>