摘要:前綴表示私有變量上述代碼實現的并不實用,因為實際上我們需要的是監聽的對象數據發生改變時才執行相應的方法。我們使用來約束遍歷的最大次數,在中默認次數為。
$watch 和 $digest
$watch 和 $digest 是數據綁定中的核心概念:我們可以使用 $watch 在 scope 中綁定 watcher 用于監聽 scope 中發生的變化,而 $digest 方法的執行即是遍歷 scope 上綁定的所有 watcher,并執行相應的 watch(指定想要監控的對象) 和 listener(當數據改變時觸發的回調) 方法。
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 并不實用,因為實際上我們需要的是:監聽的對象數據發生改變時才執行相應的 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 不會觸發 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); } }); }循環進行臟檢查
在進行 digest 時往往會發生如下情況,即某個 watcher 執行 listener 方法會引起其他 watcher 監聽的對象數據發生改變,因此我們需要循環進行臟檢查來使變化“徹底”完成。
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); }
上述代碼只要在遍歷中發現臟值,就會多循環一輪直到沒有發現臟值為止,我們考慮這樣的情況:即是兩個 watcher 之間互相影響彼此,則會導致無限循環的問題。
我們使用 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 變量來對這部分代碼的性能進行優化。
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; }深淺臟檢查
目前為止我們實現的臟檢查,僅能監聽到值的變化(淺臟檢查),無法判斷引用內部數據發生的變化(深臟檢查)。
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)); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/84389.html
摘要:但如果一個組件在生命周期鉤子里改變父組件屬性,卻是可以的,因為這個鉤子函數是在更新父組件屬性變化之前調用的注即第步,在第步之前調用。 原文鏈接:Angular.js’ $digest is reborn in the newer version of Angular showImg(https://segmentfault.com/img/remote/146000001468785...
摘要:本文將解釋引起這個錯誤的內在原因,檢測機制的內部原理,提供導致這個錯誤的共同行為,并給出修復這個錯誤的解決方案。這一次過程稱為。這個程序設計為子組件拋出一個事件,而父組件監聽這個事件,而這個事件會引起父組件屬性值發生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...
摘要:但實際上這時程序并沒有計算手續費。經過排查并查閱文檔之后,發現是的問題。本文沒有具體介紹和管道,關于這部分可以參考文中給出的鏈接 事情起源于在項目中遇到的一個小問題:項目中需要一個輸入框輸入賣出產品數量,并且在用戶輸入后根據輸入數據計算手續費。很自然的我用了ng-model和ng-change,并且一般情況下沒什么問題。問題是:輸入框下還有一個按鈕是全部賣出,點擊這個按鈕程序會自動設置...
閱讀 804·2021-09-22 16:01
閱讀 2095·2021-08-20 09:37
閱讀 1700·2019-08-30 15:54
閱讀 1699·2019-08-30 15:44
閱讀 843·2019-08-28 18:23
閱讀 3021·2019-08-26 12:17
閱讀 1023·2019-08-26 11:56
閱讀 1546·2019-08-23 16:20