前端開發框架總結之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>