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