創(chuàng)建你自己的AngularJS-第一部分 Scopes (3)

基于值的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)行處理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容