Angularjs1.X中兩種不同的雙向數據綁定
聊聊 Angularjs1.x中那些活見鬼的事情。
一. html與Controller中的雙向數據綁定
html-Controller的雙向數據綁定,在開發中非常常見,也是Angularjs1.x的宣傳點之一,使用中并沒有太多問題。
1.1數據從html流向controller
也就是從 視圖層 流向 模型層 ,原生html中需要使用表單元素(例如 input 標簽)來收集用戶輸入信息,Angularjs中通過在表單元素上使用 ng-model 標簽,當用戶輸入信息時,同步將用戶輸入的信息賦值給controller中的變量:
<body ng-app="myApp">  <div id="main" ng-controller="myCtrl">    <p>改變輸出值:</p>    <input type="text" ng-model="testInfo.content" ng-change="showInput()">  </div> <script src="./angular.min.js"></script> <script>  angular.module('myApp',[])  .controller('myCtrl',['$scope',function($scope){    $scope.showInput = function() {     console.log($scope.testInfo.content);    }  }]); </script></body>在頁面上輸入1234567即可看到,每次在頁面輸入數字后,控制臺輸出的 $scope,testInfo.content 的值都和頁面保持一致:

1.2 數據從controller流向html
也就是從 模型層 流向 數據層 ,當controller中的數據模型變量發生變化后,Angularjs又會根據數據模型的值去改變 ng-model 指令綁定的表單元素的值,使用 ng-bind 指令也可以被動獲得來自controller的數據流。
我們編寫如下demo進行測試:
<body ng-app="myApp">  <div id="main" ng-controller="myCtrl">    <button ng-click="add()">+1</button>    <p>改變輸出值:</p>    <input type="text" ng-model="testInfo.content">    <p>使用ng-bind綁定的標簽:</p>    <p ng-bind="testInfo.content"></p>  </div>  <script src="./angular.min.js"></script>  <script>  angular.module('myApp', [])    .controller('myCtrl', ['$scope', function($scope) {      //初始化      $scope.testInfo = {        content: 0      }      $scope.add = function () {        $scope.testInfo.content += 1;        console.log($scope.testInfo.content);      }    }]);  </script></body>demo中,每次點擊 +1 按鈕, $scope.testInfo.content 的值會增加1,我們可以看到頁面上的結果:

1.3 你丫倒是刷視圖啊
來看看第一個 活見鬼 的例子,demo跟上面很類似,只是將 鼠標點擊觸發 的方式改成了 定時器自動觸發 :
<body ng-app="myApp">  <div id="main" ng-controller="myCtrl">    <button ng-click="add()">+1</button>    <p>改變輸出值:</p>    <input type="text" ng-model="testInfo.content">    <p>使用ng-bind綁定的標簽:</p>    <p ng-bind="testInfo.content"></p>  </div>  <script src="./angular.min.js"></script>  <script>  angular.module('myApp', [])    .controller('myCtrl', ['$scope', function($scope) {      //初始化      $scope.testInfo = {        content: 0      }      //定時自增      setInterval(function () {       $scope.testInfo.content += 1;       console.log('$scope.testInfo.content的值現在是:',$scope.testInfo.content);      },1000)    }]);  </script></body>你會活見鬼地發現,數據模型一直在變,但是頁面卻沒有刷新:

這里就是 Angularjs1.X 雙向數據綁定中的第一個坑 ,你會發現 $scope上綁定的數據模型 和 html中顯示的內容 有時候并不是實時關聯的。這其實和 Angularjs1.X 的執行機制有關系。
如果我們自己來考慮,javascript中有一個變量的值發生了變化,現在要將這個值同步到html頁面上,需要怎么做呢?我們需要獲取到這個DOM元素,然后改變它的 innerHTML 屬性,如果是表單元素就修改 value 。其實 Angularjs 也是這樣做的,只不過使用了自己的封裝的方法―― $apply() 。那么此處的問題其實就在于,在 setInterval 的回調函數中去修改數據模型的值時,沒有觸發 $apply() 方法來更新視圖,而通過調用 Angularjs 封裝的 ng-* 方法(例如 ng-click 點擊方法)來修改視圖模型時,會自動觸發 $apply() 方法,視圖也就同步刷新了。
解決方案1
使用 Angularjs 封裝過的 $interval 服務來實現定時任務,感興趣的讀者可以自己看一下 Angularjs 源碼中 $intervalProvider 的部分,就會發現在方法最后的地方調用了 $rootScope.$apply() 。
解決方案2
如果依然使用javascript原生的定時方法,那么則需要在修改完視圖的數據模型后,手動調用 $scope.$apply() 方法來將數據模型的變動同步到html頁面中。
二. Controller與Directive中的雙向數據綁定
除了controller與html中的雙向綁定, Angularjs 中還有另一個 雙向數據綁定 ,那就是controller與directive之間的 綁定 。綁定的形式有很多種,我們先來看一下最常見的 雙向綁定 。
2.1 directive中的雙向數據綁定
在設定自定義指令的 scope 參數時,將屬性的值設置為 = 就可以實現雙向數據綁定,這里API的解釋是:
父級controller中的指定變量會與自定義指令link函數中的變量 相互影響 。
下面的實例中,我們將看看controller中的數據模型 $scope.testInfo.content 的值與自定義指令中 scope.pagination 如何相互影響,是否如定義所說這里的綁定真的是雙向的。示例界面如下(demo源碼請見附件 demo.html 文件):

+1 按鈕, Scope.testInfo.content 的值都會增加1show $scope.testInfo ,控制臺都會打印出 $scope.testInfo 的值scope.pagination 的值,并將該值進行自增接下來的測試操作,我們將按照如下的流程進行:
2.2 你丫怎么又不刷新了
隨著上一節的操作步驟,我們一起來見證 雙向數據綁定 中又一次鬧鬼事件:
點擊5次 +1 按鈕,再點擊5次數字標簽
結果為:

我們看到,第一次點擊數字標簽時,控制臺打出了link函數中 scope.pagination 的值為5,這說明 $scope.testInfo.content 的值被傳遞給了自定義指令中的 scope.pagination ,也就是說數據從controller流向了directive 。而當我們再點擊4次數字標簽(一共點了5次)后,從控制臺可以看出, scope.pagination 的值已經成為10,而頁面上使用 ng-bind 指令獲取到的結果卻依舊是 5 。也就是說, 數據從沒有從directive流向controller 。是不是有一種被騙的感覺?別著急,接著看。
點擊 show $scope.testInfo 按鈕
結果為:

當我們點擊 show $scope.testInfo 時,控制臺打印出了$scope.testInfo.content的值為5,這下證據坐實了,明明說好的雙向數據綁定,然而當自定義指令中的 scope.pagination 改變時, $scope.testInfo.content 并沒有跟著一起改變。 But!!!! 我們會發現,這個 show $scope.testInfo 點下去以后,頁面上通過 ng-bind 綁定的值卻變成了 10 。也就是說, 數據又從directive流回了controller 。
官方建議使用 $watch 方法來追蹤scope中的變量,而當我們這樣做時,會發現 $watch 函數 僅能追蹤到那些通過修改controller中的數據模型而影響link函數中變量的行為并更新視圖 。
這里就是 Angularjs1.X 雙向數據綁定中的第二個坑,controller和directive中所謂的 雙向數據綁定 ,并不能追蹤指定變量的所有變化,而且不是同步完成的。
其實這里的問題仍然和 Angularjs 的運行機制有關,解決方案如下:
解決方案1
使用自定義指令的 templateUrl 屬性替換當前指令的模板,使用 ng-click 指令來綁定一個點擊響應函數,在響應函數中改變 scope.piganation 的值。
解決方案2
在手動綁定的監聽回調中,修改自定義指令作用域內的變量后,使用 scope.$emit( ) 方法通知其父級controller,并在controller中使用 $scope.$on( ) 方法監聽同名事件,并修改對應的數據模型的值。
解決方案3
每當改變自定義指令中的變量值后,調用 scope.$apply() 方法,將directive中的變量值同步至controller的數據模型以及頁面。
三.原理和實戰總結
3.1 Angularjs中雙向數據綁定的基本原理
Angularjs中的雙向數據綁定,是通過一種叫做* "臟循環檢查(dirty-checking)" 的機制實現的。
其基本過程是這樣的,每當我們使用 ng-model 或 ng-bind 指令將數據模型中的某個變量值和html頁面上某個標簽的內容聯系起來時,Angular就會把這些變量放進一個 WatchCollection 的集合中,并自動幫我們來監控這些變量。每當 WatchCollection 中有變量出現變動時,Angular就會遍歷 WatchCollection 來查看是否有其他監控中的變量也被影響,每當有一個變量被影響,Angular都會在遍歷后再進行一次遍歷,直到某一次遍歷后 WatchCollection 中的變量都沒有變化,則Angular會認為當前的改動已經穩定了,然后才會將數據模型的變化同步到DOM元素上去,也就實現了數據綁定。
我們可以把 WatchCollection 理解為當前頁面的一種抽象,其中包含著頁面上所有有可能發生變化的部分。
3.2 雙向數據綁定的實踐經驗
想要在 Angularjs 項目中更加穩定地使用雙向數據綁定,筆者的建議是:
在 Angularjs 項目中,盡可能地使用Angular告訴你的方式去編寫所希望實現的功能。
我們可以回顧一下上面在使用雙向數據綁定發生異常時的場景:
$interval , $timeout 服務)ng-click 來實現點擊事件的監聽)你會發現,每當自己沒有按照Angular的方式去編寫代碼,或者沒有按照一個模塊設計的初衷去使用它時,就無法確切地得到期望的結果。這是很容易理解的,如果你沒有按照Angular要求的方式書寫代碼,憑什么期望它對你的代碼做出100%正確的回應呢?至于上述兩種數據綁定中出現問題的解決方案,上文已經有所提及,此處不再贅述。
許多人都聽說過 "盡量不要在controller中操作DOM" 這句話,實際上它并不意味著你在controller中操作DOM會導致程序報錯,而是在說 如果你同時使用 jQuery 和 Angular 兩套系統來管理自己的代碼,但又沒有按照官方指定的方式來規避它們之間的沖突,那代碼很可能會變得不穩定。 想想當年 騰訊電腦管家 和 360安全衛士 將你的電腦卡死的場景,你就明白這樣做的結果了。
四. 小結――所謂高手
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。
新聞熱點
疑難解答