AngularJs1.x解讀 $watch 和 $digest

Scope object

Scope對象其實就是一個簡單的POJO(plain old JavaScript Object)。咱們能夠給它任意的添加屬性。javascript

// scope.js
export default class Scope {

}
// test.js
const scope = new Scope();
scope.aProperty = 1;
expect(scope.aProperty).toBe(1);

$watch$digest

$watch$digest就像一個硬幣的兩面。他們組合在一塊兒就是髒檢查循環的核心:對於數據變化的響應。java

$watch:

$watch(watchExpression, listener, [objectEquality]);
  • watchExpression: 監聽的數據
  • listener:數據發生變化的時候調用
  • objectEquality: 後面單獨說

angularjs中將全部的 watchExpression 存放到一個叫做$$watcher的數組中,所以咱們建立一個數組:angularjs

$$watchers = [];

$watch(watchFn, listenerFn) {

    const watcher = {
      watchFn,
      listenerFn
    };
    
    this.$$watchers.push(watcher);
}

$digest:

它遍歷scope上的全部watchers,計算 watchExpression ,而且調用它對應的 listenerFn。數組

_.forEach(this.$$watchers, watcher => {
  watcher.listenerFn();
});

髒值檢測

目的:只有監控的值發生改變的時候咱們才執行對應的listener。
思路:存儲上一次的值,和這一次值的進行比對。函數

$digest() {
    // 將變量聲明提取到循環外面
    let newValue;
    let oldValue;
    
    _.forEach(this.$$watchers, watcher => {
      
      newValue = watcher.watchFn(this);
      // 第一次獲取last的時候值爲undefined
      oldValue = watcher.last;
      // 只有當新舊值不相等的時候才執行listener
      if (newValue !== oldValue) {
        watcher.last = newValue;
        watcher.listenerFn(newValue, oldValue, this);
      }
    });
}

初始化watch的值

angularjs中的初始化值爲一個函數:ui

function initWatchVal() {}

而後將其賦值給watch的last:this

const watcher = {
  watchFn,
  listenerFn,
  last: initWacthVal
};

持續監測

添加一個幫助方法,將全部的watchFn運行一次,返回一個boolean值,表示是否有更新:code

$digest() {
  
    let newValue;
    let oldValue;
    // 標記是否爲髒
    let dirty;
    // 上來先執行一次看是否全部值發生變化,若是有變化,則第二次執行watch
    do {
      // 初次進來設置爲false
      dirty = false;
    
      _.forEach(this.$$watchers, watcher => {
      
        newValue = watcher.watchFn(this);
        // 第一次獲取last的時候值爲undefined
        oldValue = watcher.last;
        // 只有當新舊值不相等的時候才執行listener
        if (newValue !== oldValue) {
          dirty = true;
          watcher.last = newValue;
          // watcher.listenerFn(newValue, oldValue, this);
          const temp = oldValue === initWacthVal ? newValue : oldValue;
          watcher.listenerFn(newValue, temp, this);
        }
      });
    
    } while (dirty);
    
}

ttl

若是watch一直爲不穩定的值,咱們須要中止髒檢查。angularjs中默認的ttl爲10,對外暴露可修改。對象

// 若是爲髒,而且ttl達到0的時候
if (dirty && !(ttl--)) {
    throw '10 digest iterations reached';
}

中止髒檢查

添加lastDirtyWatch去判斷,看源碼。ip

  • 執行$digest的時候將 lastDirtyWatch = null
  • 當前watcer 和 lastDirtyWatch 相同的時候設置爲 null
  • watch的listener裏面包含有watch的時候,重置爲 null

基於值的髒檢查

$watch(watchExpression, listener, [objectEquality]);

判斷是否相等要包含數組和對象,angularjs內置的方法爲:

angular.equals(o1, o2);

而後替換原來的新舊值的判斷:

// 判斷是否相等
function areEqual(newValue, oldValue, valueEq) {
  
  if (valueEq) {
    return _.isEqual(newValue, oldValue);
  } else {
    return newValue === oldValue;
  }

}

銷燬監聽

angularjs中的銷燬是給$watch的時候返回一個方法,當你調用的時候,直接將它從數組中移除。

$watch(watchFn, listenerFn, valueEq) {

    const watcher = {
      watchFn,
      listenerFn,
      valueEq, 
      last: initWacthVal
    };
    
    this.$$watchers.push(watcher);
    // 新加入一個watcher的時候將lastDirtyWatch初始化
    this.lastDirtyWatch = null;

    return () => {
      const index = _.find(this.$$watchers, watcher);
      if (index >= 0) {
        this.$$watchers.splice(index, 1);
      }
    };
}
對內簡單的分享,記錄下來。參照書名: build your own angularjs
相關文章
相關標籤/搜索