成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Angular 學習筆記:$digest 實現原理

baiy / 3003人閱讀

摘要:前綴表示私有變量上述代碼實現的并不實用,因為實際上我們需要的是監(jiān)聽的對象數據發(fā)生改變時才執(zhí)行相應的方法。我們使用來約束遍歷的最大次數,在中默認次數為。

$watch 和 $digest

$watch$digest 是數據綁定中的核心概念:我們可以使用 $watch 在 scope 中綁定 watcher 用于監(jiān)聽 scope 中發(fā)生的變化,而 $digest 方法的執(zhí)行即是遍歷 scope 上綁定的所有 watcher,并執(zhí)行相應的 watch(指定想要監(jiān)控的對象) 和 listener(當數據改變時觸發(fā)的回調) 方法。

function Scope {
    this.$$watchers = []; // $$ 前綴表示私有變量
}

Scope.prototye.$watch = function(watchFn, listenerFn) {
    let watcher = {
        watchFn: watchFn,
        listenerFn: listenerFn,
    };

    this.$$watchers.push(watcher);
}

Scope.prototype.$digest = function() {
    this.watchers.forEach((watcher) => {
        watcher.listenerFn();
    });
}

上述代碼實現的 $digest 并不實用,因為實際上我們需要的是:監(jiān)聽的對象數據發(fā)生改變時才執(zhí)行相應的 listener 方法

臟檢查
Scope.prototype.$digest = function() {
    let self = this;
    let newValue, oldValue;
    this.watchers.forEach((watcher) => {
        newValue = watcher.watchFn(self);
        oldValue = watcher.last;
        if (newValue !== oldValue) {
            watch.last = newValue;
            watcher.listenerFn(newValue, oldValue, self);
        }
    });
}

上述代碼在大部分情況下可以正常運行,但是當我們首次遍歷 watcher 對象時其 last 變量值為 undefined,這樣會導致如果 watcher 的第一個有效值同為 undefined 不會觸發(fā) listener 方法。

console.log(undefined === undefined) // true

我們使用 initWatchVal 方法解決這個問題.

function initWatchVal() {
  // TODO
}

Scope.prototye.$watch = function(watchFn, listenerFn) {
  let watcher = {
      watchFn: watchFn,
      listenerFn: listenerFn || function() {},
      last: initWatchVal
  };

  this.$$watchers.push(watcher);
}

Scope.prototype.$digest = function() {
  let self = this;
  let newValue, oldValue;
  this.watchers.forEach((watcher) => {
      newValue = watcher.watchFn(self);
      oldValue = watcher.last;
      if (newValue !== oldValue) {
          watch.last = newValue;
          watcher.listenerFn(newValue, oldValue === initWatchVal ? newValue : oldValue, self);
      }
  });
}
循環(huán)進行臟檢查

在進行 digest 時往往會發(fā)生如下情況,即某個 watcher 執(zhí)行 listener 方法會引起其他 watcher 監(jiān)聽的對象數據發(fā)生改變,因此我們需要循環(huán)進行臟檢查來使變化“徹底”完成。

Scope.prototype.$$digestOnce = function() {
    let self = this;
    let newValue, oldValue, dirty;
    this.watchers.forEach((watcher) => {
        newValue = watcher.watchFn(self);
        oldValue = watcher.last;
        if (newValue !== oldValue) {
            dirty = true;
            watch.last = newValue;
            watcher.listenerFn(newValue, oldValue === initWatchVal ? newValue : oldValue, self);
        }
    });
    
    return dirty;
}

Scope.prototype.$digest = function() {
    let dirty;
    do { dirty = this.$$digestOnce(); }
    while (dirty);
}

上述代碼只要在遍歷中發(fā)現臟值,就會多循環(huán)一輪直到沒有發(fā)現臟值為止,我們考慮這樣的情況:即是兩個 watcher 之間互相影響彼此,則會導致無限循環(huán)的問題。

我們使用 TTL(Time to Live)來約束遍歷的最大次數,在 Angular 中默認次數為10。

Scope.prototype.$digest = function() {
    let dirty;
    let ttl = 10;
    do {
        dirty = this.$$digestOnce();
        if (dirty && !(ttl--)) {
            throw "10 digest iterations reached.";
        }
    } while (dirty)
}

同時,在每次 digest 的最后一輪遍歷沒有必要對全部 watcher 進行檢查,我們通過使用 $$lastDirtyWatch 變量來對這部分代碼的性能進行優(yōu)化。

function Scope {
    this.$$watchers = [];
    this.$$lastDirtyWatch = null;
}

Scope.prototype.$digest = function() {
    let dirty;
    let ttl = 10;

    this.$$lastDirtyWatch = null;

    do {
        dirty = this.$$digestOnce();
        if (dirty && !(ttl--)) {
            throw "10 digest iterations reached.";
        }
    } while (dirty)
}

Scope.prototype.$$digestOnce = function() {
    let self = this;
    let newValue, oldValue, dirty;
    this.watchers.forEach((watcher) => {
        newValue = watcher.watchFn(self);
        oldValue = watcher.last;
        if (newValue !== oldValue) {
            self.$$lastDirtyWatch = watcher;
            dirty = true;
            watch.last = newValue;
            watcher.listenerFn(newValue, oldValue === initWatchVal ? newValue : oldValue, self);
        } else if (self.$$lastDirtyWatch === watcher) {
            return false;
        }
    });

    return dirty;
}

同時為了避免 $watch 嵌套使用帶來的不良影響,我們需要在每次添加 watcher 時重置 $$lastDirtyWatch:

Scope.prototye.$watch = function(watchFn, listenerFn) {
    let watcher = {
        watchFn: watchFn,
        listenerFn: listenerFn || function() {},
        last: initWatchVal
    };

    this.$$watchers.push(watcher);
    this.$$lastDirtyWatch = null;
}
深淺臟檢查

目前為止我們實現的臟檢查,僅能監(jiān)聽到值的變化(淺臟檢查),無法判斷引用內部數據發(fā)生的變化(深臟檢查)。

Scope.prototye.$watch = function(watchFn, listenerFn, valueEq) {
    let watcher = {
        watchFn: watchFn,
        listenerFn: listenerFn || function() {},
        valueEq: !!valueEq,
        last: initWatchVal
    };

    this.$$watchers.push(watcher);
    this.$$lastDirtyWatch = null;
}
Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) {
  if (valueEq) {
      return _.isEqual(newValue, oldValue);
  } else {
      return newValue === oldValue;
  }
}
Scope.prototype.$$digestOnce = function() {
  let self = this;
  let newValue, oldValue, dirty;
  this.watchers.forEach((watcher) => {
      newValue = watcher.watchFn(self);
      oldValue = watcher.last;
      if (!self.$$areEqual(newValue, oldValue, watcher.valueEq)) {
          self.$$lastDirtyWatch = watcher;
          dirty = true;
          watch.last = watcher.valueEq ? _.cloneDeep(newValue) : newValue;
          watcher.listenerFn(newValue, oldValue === initWatchVal ? newValue : oldValue, self);
      } else if (self.$$lastDirtyWatch === watcher) {
          return false;
      }
  });

  return dirty;
}
NaN 的兼容考慮

需要注意的是,NaN 不等于其自身,所以在判斷 newValue 與 oldValue 是否相等時,需要特別考慮。

Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) {
    if (valueEq) {
        return _.isEqual(newValue, oldValue);
    } else {
        return newValue === oldValue ||
            (typeof newValue === "number" && typeof oldValue === "number" && isNaN(newValue) && isNaN(oldValue));
    }
}

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/84389.html

相關文章

  • [譯] $digestAngular 中重生

    摘要:但如果一個組件在生命周期鉤子里改變父組件屬性,卻是可以的,因為這個鉤子函數是在更新父組件屬性變化之前調用的注即第步,在第步之前調用。 原文鏈接:Angular.js’ $digest is reborn in the newer version of Angular showImg(https://segmentfault.com/img/remote/146000001468785...

    incredible 評論0 收藏0
  • [譯] 關于 `ExpressionChangedAfterItHasBeenCheckedErro

    摘要:本文將解釋引起這個錯誤的內在原因,檢測機制的內部原理,提供導致這個錯誤的共同行為,并給出修復這個錯誤的解決方案。這一次過程稱為。這個程序設計為子組件拋出一個事件,而父組件監(jiān)聽這個事件,而這個事件會引起父組件屬性值發(fā)生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...

    andong777 評論0 收藏0
  • angular1學習筆記,view model 的同步過程

    摘要:但實際上這時程序并沒有計算手續(xù)費。經過排查并查閱文檔之后,發(fā)現是的問題。本文沒有具體介紹和管道,關于這部分可以參考文中給出的鏈接 事情起源于在項目中遇到的一個小問題:項目中需要一個輸入框輸入賣出產品數量,并且在用戶輸入后根據輸入數據計算手續(xù)費。很自然的我用了ng-model和ng-change,并且一般情況下沒什么問題。問題是:輸入框下還有一個按鈕是全部賣出,點擊這個按鈕程序會自動設置...

    Forelax 評論0 收藏0
  • 面試題總結

    摘要:異步管理等到執(zhí)行完成后返回種狀態(tài),代表成功,代表失敗。我們在函數內聲明的變量叫局部變量,局部變量只能在里面訪問,外面是訪問不到的。那么就是為解決這一問題的。可以用鏈式寫法等到異步有結果再進行下一步。 1. vue的雙向綁定原理: vue的雙向綁定原理是通過Object.definedProperty的getter和setter來對屬性進行數據劫持的。 因為Object.definedP...

    weizx 評論0 收藏0
  • 面試題總結

    摘要:異步管理等到執(zhí)行完成后返回種狀態(tài),代表成功,代表失敗。我們在函數內聲明的變量叫局部變量,局部變量只能在里面訪問,外面是訪問不到的。那么就是為解決這一問題的??梢杂面準綄懛ǖ鹊疆惒接薪Y果再進行下一步。 1. vue的雙向綁定原理: vue的雙向綁定原理是通過Object.definedProperty的getter和setter來對屬性進行數據劫持的。 因為Object.definedP...

    enali 評論0 收藏0

發(fā)表評論

0條評論

baiy

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<