AngularJS 中的髒資料檢查
我們將介紹如何在 AngularJs 中進行髒資料檢查。
在 AngularJS 中實現髒資料檢查
AngularJS 對雙向資料繫結的 $scope
變數執行髒資料檢查。Ember.js
通過以程式設計方式修復 setter 和 getter 來執行雙向資料繫結,但在 Angular 中,髒資料檢查允許它查詢可能可用或不可用的變數。
# AngularJs
$scope.$watch( wExp, listener, objEq );
$scope.$watch
函式用於我們想要檢視變數何時被更改。我們必須選擇三個引數來應用這個函式:wExp
是我們想要觀看的內容,listener
是我們希望它在更新時執行的操作,以及我們想要檢查變數還是物件。
當我們想要檢查一個變數時,我們可以在使用這個函式時跳過這個。讓我們看一個例子,如下所示。
#AngularJs
$scope.newName = 'Subhan';
$scope.$watch( function( ) {
return $scope.name;
}, function( newVal, oldVal ) {
console.log('$scope.newName is updated!');
} );
Angular 會將你的 watcher 函式儲存在 $scope
中。你可以通過將 $scope
記錄到控制檯來檢查這一點。
在 $watch
中,我們還可以使用字串代替函式。這也將與函式的工作方式相同。
當我們在 Angular 原始碼中新增一個字串時,程式碼將是:
#AngularJs
if (typeof wExp == 'string' && get.constant) {
var newFn = watcher.fn;
watcher.fn = function(newVal, oldVal, scope) {
newFn.call(this, newVal, oldVal, scope);
arrayRemove(array, watcher);
};
}
通過應用此程式碼,wExp
將被設定為一個函式。這將使用具有我們選擇的名稱的變數指定我們的偵聽器。
AngularJS 中的 $watchers
函式
所有選定的觀察者都將儲存在 $scope
中的 $$watchers
變數中。當我們檢查 $$watchers
時,你會發現一個物件陣列,如下所示。
#AngularJs
$$watchers = [
{
eq: false,
fn: function( newVal, oldVal ) {},
last: 'Subhan',
exp: function(){},
get: function(){}
}
];
unRegWatch
函式由 $watch
函式返回。這表明如果我們想將初始的 $scope.$watch
分配給一個變數,我們可以通過命令它停止觀察來輕鬆實現這一點。
只需確認我們已經開啟並檢視了在斷開觀察程式之前記錄的第一個 $scope
。
AngularJS 中的 $scope.$apply
函式
當我們嘗試執行 controller/directive/etc.
時,Angular 會執行一個函式,我們在其中呼叫 $scope.$watch
。在管理 rootScope
中的 $digest
函式之前,$apply
函式將執行我們選擇的函式。
Angular $apply
函式如下:
#AngularJs
$apply: function(expr) {
try {
beginPhase('$apply');
return this.$eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
clearPhase();
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
}
AngularJS 中的 expr
引數
當使用 $scope.$apply
時,有時 Angular 或者我們會通過一個函式,expr
引數就是那個函式。
在使用此功能時,我們不會發現需要最頻繁地使用 $apply
。讓我們看看當 ng-keydown
使用 $scope.$apply
時會發生什麼。
要提交指令,我們使用以下程式碼。
#AngularJs
var EventNgDirective = {};
forEach(
keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(newName) {
var newDirectiveName = newDirectiveNormalize('ng-' + newName);
EventNgDirective[newDirectiveName] = ['$parse', function($parse) {
return {
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(lowercase(newName), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
};
}
};
}];
}
);
此程式碼將遍歷可以觸發的不同型別的事件,生成一個名為 ng(eventName)
的新指令。當涉及指令的編譯功能時,事件處理程式被記錄在元素上,其中事件包含指令的名稱。
當這個事件被觸發時,Angular 將執行 $scope.$apply
,為其提供一個執行函式。
這就是 $scope
值將如何使用元素值更新的方式,但此繫結只是單向繫結。這樣做的原因是我們應用了 ng-keydown
,它允許我們僅在應用此事件時進行更改。
結果,得到了一個新的值!
我們的主要目標是實現雙向資料繫結。為了實現這個目標,我們可以使用 ng-model
。
為了使 ng-model
發揮作用,它同時使用 $scope.$watch
和 $scope.$apply
。
我們給出的輸入將通過 ng-model
加入事件處理程式。此時,呼叫了 $scope.$watch
!
$scope.$watch
在指令的控制器中被呼叫。
#AngularJs
$scope.$watch(function ngModelWatch() {
var value = ngModelGet($scope);
if (ctrl.$modelValue !== value) {
var formatters = ctrl.$formatters,
idx = formatters.length;
ctrl.$modelValue = value;
while(idx--) {
value = formatters[idx](value);
}
if (ctrl.$viewValue !== value) {
ctrl.$viewValue = value;
ctrl.$render();
}
}
return value;
});
當設定 $scope.$watch
時僅使用一個引數時,我們選擇的函式將被應用,即使它是否已更新。ng-model
中的函式檢查模型和檢視是否同步。
如果不同步,該函式會給模型一個新值並更新它。當我們在 $digest
中執行這個函式時,這個函式允許我們通過返回新值來知道新值是什麼。
為什麼監聽器沒有被觸發
讓我們回到示例中的點,我們在與我們描述的類似的函式中取消註冊了 $scope.$watch
函式。我們現在可以承認沒有收到有關更新 $scope.name
的通知的原因,即使我們已經更新了它。
正如我們所知,$scope.$apply
由 Angular 在每個指令控制器函式上執行。當我們估計了指令的控制器功能時,$scope.$apply
函式將僅在一個條件下執行 $digest
。
這意味著我們在 $scope.$watch
函式能夠執行之前取消了它的註冊,因此它被呼叫的可能性很小甚至沒有。
AngularJS 中的 $digest
函式
你可以通過 $scope.$apply
在 $rootScope
上呼叫此函式。我們可以在 $rootScope
上執行摘要迴圈,然後它將越過範圍並執行摘要迴圈。
摘要迴圈將觸發我們在 $$watchers
變數中的所有 wExp
函式。它將根據最後一個已知值檢查它們。
如果結果是否定的,監聽器將被觸發。
在摘要迴圈中,當它執行時,它會迴圈兩次。一次,它將在觀察者之間迴圈,然後再次迴圈,直到迴圈不再是髒
。
當 wExp
和最後一個已知值不相等時,我們說迴圈是髒的。通常這個迴圈會執行一次,如果執行十次以上就會報錯。
Angular 可以在任何可能改變模型值的東西上執行 $scope.$apply
。
你必須執行 $scope.$apply();
當我們在 Angular 之外更新了 $scope
。這將通知 Angular 範圍已更新。
讓我們設計一個基本版本的髒資料檢查。
我們希望儲存的所有資料都將在 Scope
函式中。我們將在函式上擴充套件物件以複製 $digest
和 $watch
。
因為我們不需要評估與 Scope
有關的函式,所以我們不會使用 $apply
。我們將直接使用 $digest
。
這就是我們的 Scope
的樣子:
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( ) {
};
Scope.prototype.$digest = function( ) {
};
$watch
應該採用兩個引數,wExp
和 listener
。我們可以在 Scope
中設定值。
當 $watch
被呼叫時,我們將它們傳送到先前儲存在 Scope
中的 $$watcher
值中。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
};
在這裡,我們會注意到 listener
設定為一個空函式。
當沒有提供 listener
時,你可以按照此示例輕鬆為所有變數註冊 $watch
。
現在我們將執行 $digest
。我們可以檢查舊值和新值是否相等。
如果監聽器尚未被觸發,我們也可以觸發它。為此,我們將迴圈直到它們相等。
dirtyChecking
變數幫助我們實現了這個目標,無論值是否相等。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirtyChecking;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
現在,我們將設計一個新的範圍示例。我們將把它分配給 $scope
;然後,我們將註冊一個 watch 函式。
然後我們將更新它並消化
它。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirtyChecking;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
var $scope = new Scope();
$scope.newName = 'Subhan';
$scope.$watch(function(){
return $scope.newName;
}, function( newVal, oldVal ) {
console.log(newVal, oldVal);
} );
$scope.$digest();
我們已經成功實現了髒資料檢查。當我們觀看控制檯時,你將觀察其日誌。
#AngularJs
Subhan undefined
以前,未定義 $scope.newName
。我們已經為 Subhan
修復了它。
我們將把 $digest
函式連線到輸入上的 keyup
事件。通過這樣做,我們不必自己指定它。
這意味著我們也可以實現雙向資料繫結。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirty;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
var $scope = new Scope();
$scope.newName = 'Subhan';
var element = document.querySelectorAll('input');
element[0].onkeyup = function() {
$scope.newName = element[0].value;
$scope.$digest();
};
$scope.$watch(function(){
return $scope.newName;
}, function( newVal, oldVal ) {
console.log('Value updated In Input - The new value is ' + newVal);
element[0].value = $scope.newName;
} );
var updateScopeValue = function updateScopeValue( ) {
$scope.name = 'Butt';
$scope.$digest();
};
使用這種技術,我們可以輕鬆地更新輸入的值,如 $scope.newName
中所示。你也可以呼叫 updateScopeValue
,輸入的值會顯示出來。
Rana is a computer science graduate passionate about helping people to build and diagnose scalable web application problems and problems developers face across the full-stack.
LinkedIn