基于值的Dirty檢查
目前為止我們已經(jīng)使用嚴(yán)格的相等操作符===來比較新老值。在大多數(shù)情況下這樣就已經(jīng)是比較好的,因為它能檢測所有基本類型的變化同時能檢測一個對象或數(shù)組是否變成另一個新的對象或數(shù)組。但是Angular還有另外一種方式用來檢測對象或者數(shù)組內(nèi)部的某個屬性或者元素的變化。換句話說就是你可以監(jiān)視值的變化,而不僅僅只有引用。
這種dirty檢查是通過在$watch函數(shù)中提供一個額外的可選布爾標(biāo)志而達(dá)到的。當(dāng)標(biāo)志為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);
});
在這個測試?yán)锩娴臒o論scope.aValue數(shù)組什么時候被改變計數(shù)器都會增長。當(dāng)我們?yōu)閿?shù)組里面增加一個元素時,我們期望它能夠注意到這個變化,但是實際上它并沒有。scope.aValue依然是同一個數(shù)組,只是里面的內(nèi)容發(fā)生了變化(引用沒變,值發(fā)生了變化)。
首先讓我們重新定義$watch為其添加布爾值的標(biāo)志位并且把它存儲在監(jiān)視器中:
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;
};
在上面代碼中,我們所做的就是在監(jiān)視器中添加布爾標(biāo)志位,并通過兩次的取反操作強制確保它必為一個布爾值。當(dāng)一個用戶調(diào)用$watch而沒有創(chuàng)第三個參數(shù)時,valueEq將會是undefined,而通過兩次取反它將變成false。
基于值的dirty檢查意味著如果舊值或者新值是對象或者數(shù)組我們就必須迭代它們所包含的所有屬性或者元素。如果它們的值有任何的不同之處那么監(jiān)視器就是dirty的。如果值包含另外一個對象或者數(shù)組,那么這個對象或數(shù)組將遞歸似的比較值。
Angular使用它自己的比較檢查函數(shù),但是我們將使用Lo-Dash里面提供的函數(shù)來進(jìn)行代替,因為它已經(jīng)實現(xiàn)了我們想要的功能。讓我定義一個新的函數(shù)它使用兩個值和一個布爾標(biāo)識作為參數(shù),并將兩個值進(jìn)行比較:
src/scope.js
Scope.prototype.$$areEqual = function(newValue,oldValue,valueEq){
if(valueEq){
return _.isEqual(newValue,oldValue);
}else{
return newValue === oldValue;
}
}
為了注意到值的變化,我們還需要改變在各個監(jiān)視器中存儲舊值的方式。只存儲當(dāng)前值的引用顯然已經(jīng)不夠了,因為任何在值上的改變也將應(yīng)用于我們的監(jiān)視中。我們永遠(yuǎn)不會注意到任何更改因為$$areEqual從一開始就將得到兩個引用,并且這兩個引用的值并不會發(fā)生改變。由于這個原因我們需要對值進(jìn)行深拷貝并將其存儲。
就像Angular使用它自己的深拷貝函數(shù)進(jìn)行相等的檢查,現(xiàn)在我們將使用Lo-Dash的一個方法來實現(xiàn)。
讓我們更新$digestOnce讓它使用新的$$areEqual函數(shù)并拷貝最后一個引用:
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;
}
測試通過,現(xiàn)在我們的代碼已經(jīng)支持兩種相等檢查了。
檢查值的操作顯然比檢查引用的操作更為復(fù)雜。涉及到的內(nèi)容也更多。檢查一個嵌套數(shù)據(jù)所花費的時間,進(jìn)行一次深拷貝所占用的內(nèi)存大小。這就是為什么Angular沒有默認(rèn)進(jìn)行基于值的dirty檢查。你需要顯式地設(shè)置標(biāo)志來啟用它。
Angular還有第三種dirty檢查機制:集合監(jiān)視("Collection Watching")。我們將在第3章的時候?qū)崿F(xiàn)它。
在進(jìn)行值的比較之前,還有一個JavaScript所獨有的問題需要我們進(jìn)行處理。