基于值的Dirty檢查
目前為止我們已經使用嚴格的相等操作符===來比較新老值。在大多數情況下這樣就已經是比較好的,因為它能檢測所有基本類型的變化同時能檢測一個對象或數組是否變成另一個新的對象或數組。但是Angular還有另外一種方式用來檢測對象或者數組內部的某個屬性或者元素的變化。換句話說就是你可以監視值的變化,而不僅僅只有引用。
這種dirty檢查是通過在$watch函數中提供一個額外的可選布爾標志而達到的。當標志為true時,基于值的檢測被啟用。讓我們來添加一個測試:
test/scope_spec.js
it("conpares based on value if enabled",function(){
scope.aValue = [1,2,3];
scope.counter = 0 ;
scope.$watch(
function(scope){ return scope.aValue; }
function(newValue,oldValue,scope){
scope.counter++;
},
true
);
scope.$digest();
expect(scope.counter).toBe(1);
scope.aValue.push(4);
scope.$digest();
expect(scope.counter).toBe(2);
});
在這個測試里面的無論scope.aValue數組什么時候被改變計數器都會增長。當我們為數組里面增加一個元素時,我們期望它能夠注意到這個變化,但是實際上它并沒有。scope.aValue依然是同一個數組,只是里面的內容發生了變化(引用沒變,值發生了變化)。
首先讓我們重新定義$watch為其添加布爾值的標志位并且把它存儲在監視器中:
src/scope.js
Scope.prototype.$watch = function(watchFn,listenerFn,valueEq){
var watcher = {
watchFn : watchFn,
listenerFn : listenerFn || function(){},
valueEq : !!valueEq,
last : initWatchVal
};
this.$$watchers.push(watcher);
this.$$lastDirtyWatch = null;
};
在上面代碼中,我們所做的就是在監視器中添加布爾標志位,并通過兩次的取反操作強制確保它必為一個布爾值。當一個用戶調用$watch而沒有創第三個參數時,valueEq將會是undefined,而通過兩次取反它將變成false。
基于值的dirty檢查意味著如果舊值或者新值是對象或者數組我們就必須迭代它們所包含的所有屬性或者元素。如果它們的值有任何的不同之處那么監視器就是dirty的。如果值包含另外一個對象或者數組,那么這個對象或數組將遞歸似的比較值。
Angular使用它自己的比較檢查函數,但是我們將使用Lo-Dash里面提供的函數來進行代替,因為它已經實現了我們想要的功能。讓我定義一個新的函數它使用兩個值和一個布爾標識作為參數,并將兩個值進行比較:
src/scope.js
Scope.prototype.$$areEqual = function(newValue,oldValue,valueEq){
if(valueEq){
return _.isEqual(newValue,oldValue);
}else{
return newValue === oldValue;
}
}
為了注意到值的變化,我們還需要改變在各個監視器中存儲舊值的方式。只存儲當前值的引用顯然已經不夠了,因為任何在值上的改變也將應用于我們的監視中。我們永遠不會注意到任何更改因為$$areEqual從一開始就將得到兩個引用,并且這兩個引用的值并不會發生改變。由于這個原因我們需要對值進行深拷貝并將其存儲。
就像Angular使用它自己的深拷貝函數進行相等的檢查,現在我們將使用Lo-Dash的一個方法來實現。
讓我們更新$digestOnce讓它使用新的$$areEqual函數并拷貝最后一個引用:
src/scope.js
Scope.prototype.$$digestOnce = function(){
var self = this;
var newValue,oldValue,dirty;
_.forEach(this.$$watchers,function(watcher){
newValue = watcher.watchFn(self);
oldValue = watcher.last;
if(!self.$$areEqual(newValue,oldValue,watcher.valueEq)){
self.$$lastDirtyWatch = watcher;
watcher.last = (watcher.valueEq ? _.cloneDeep(newValue) : newValue);
watcher.listenerFn(newValue,
(oldValue === initWatchVal ? newValue : oldValue),
self);
dirty = true;
}else if(self.$$lastDirtyWatch === watcher){
return false;
}
});
return dirty;
}
測試通過,現在我們的代碼已經支持兩種相等檢查了。
檢查值的操作顯然比檢查引用的操作更為復雜。涉及到的內容也更多。檢查一個嵌套數據所花費的時間,進行一次深拷貝所占用的內存大小。這就是為什么Angular沒有默認進行基于值的dirty檢查。你需要顯式地設置標志來啟用它。
Angular還有第三種dirty檢查機制:集合監視("Collection Watching")。我們將在第3章的時候實現它。
在進行值的比較之前,還有一個JavaScript所獨有的問題需要我們進行處理。