摘要:前綴表示私有變量上述代碼實現(xiàn)的并不實用,因為實際上我們需要的是監(jiān)聽的對象數(shù)據(jù)發(fā)生改變時才執(zhí)行相應(yīng)的方法。我們使用來約束遍歷的最大次數(shù),在中默認次數(shù)為。
$watch 和 $digest
$watch 和 $digest 是數(shù)據(jù)綁定中的核心概念:我們可以使用 $watch 在 scope 中綁定 watcher 用于監(jiān)聽 scope 中發(fā)生的變化,而 $digest 方法的執(zhí)行即是遍歷 scope 上綁定的所有 watcher,并執(zhí)行相應(yīng)的 watch(指定想要監(jiān)控的對象) 和 listener(當數(shù)據(jù)改變時觸發(fā)的回調(diào)) 方法。
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(); }); }
上述代碼實現(xiàn)的 $digest 并不實用,因為實際上我們需要的是:監(jiān)聽的對象數(shù)據(jù)發(fā)生改變時才執(zhí)行相應(yīng)的 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)聽的對象數(shù)據(jù)發(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ā)現(xiàn)臟值,就會多循環(huán)一輪直到?jīng)]有發(fā)現(xiàn)臟值為止,我們考慮這樣的情況:即是兩個 watcher 之間互相影響彼此,則會導致無限循環(huán)的問題。
我們使用 TTL(Time to Live)來約束遍歷的最大次數(shù),在 Angular 中默認次數(shù)為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; }深淺臟檢查
目前為止我們實現(xiàn)的臟檢查,僅能監(jiān)聽到值的變化(淺臟檢查),無法判斷引用內(nèi)部數(shù)據(jù)發(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)); } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/84389.html
摘要:但如果一個組件在生命周期鉤子里改變父組件屬性,卻是可以的,因為這個鉤子函數(shù)是在更新父組件屬性變化之前調(diào)用的注即第步,在第步之前調(diào)用。 原文鏈接:Angular.js’ $digest is reborn in the newer version of Angular showImg(https://segmentfault.com/img/remote/146000001468785...
摘要:本文將解釋引起這個錯誤的內(nèi)在原因,檢測機制的內(nèi)部原理,提供導致這個錯誤的共同行為,并給出修復這個錯誤的解決方案。這一次過程稱為。這個程序設(shè)計為子組件拋出一個事件,而父組件監(jiān)聽這個事件,而這個事件會引起父組件屬性值發(fā)生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...
摘要:但實際上這時程序并沒有計算手續(xù)費。經(jīng)過排查并查閱文檔之后,發(fā)現(xiàn)是的問題。本文沒有具體介紹和管道,關(guān)于這部分可以參考文中給出的鏈接 事情起源于在項目中遇到的一個小問題:項目中需要一個輸入框輸入賣出產(chǎn)品數(shù)量,并且在用戶輸入后根據(jù)輸入數(shù)據(jù)計算手續(xù)費。很自然的我用了ng-model和ng-change,并且一般情況下沒什么問題。問題是:輸入框下還有一個按鈕是全部賣出,點擊這個按鈕程序會自動設(shè)置...
摘要:異步管理等到執(zhí)行完成后返回種狀態(tài),代表成功,代表失敗。我們在函數(shù)內(nèi)聲明的變量叫局部變量,局部變量只能在里面訪問,外面是訪問不到的。那么就是為解決這一問題的??梢杂面準綄懛ǖ鹊疆惒接薪Y(jié)果再進行下一步。 1. vue的雙向綁定原理: vue的雙向綁定原理是通過Object.definedProperty的getter和setter來對屬性進行數(shù)據(jù)劫持的。 因為Object.definedP...
摘要:異步管理等到執(zhí)行完成后返回種狀態(tài),代表成功,代表失敗。我們在函數(shù)內(nèi)聲明的變量叫局部變量,局部變量只能在里面訪問,外面是訪問不到的。那么就是為解決這一問題的??梢杂面準綄懛ǖ鹊疆惒接薪Y(jié)果再進行下一步。 1. vue的雙向綁定原理: vue的雙向綁定原理是通過Object.definedProperty的getter和setter來對屬性進行數(shù)據(jù)劫持的。 因為Object.definedP...
閱讀 796·2021-09-22 16:01
閱讀 2093·2021-08-20 09:37
閱讀 1696·2019-08-30 15:54
閱讀 1697·2019-08-30 15:44
閱讀 835·2019-08-28 18:23
閱讀 3017·2019-08-26 12:17
閱讀 1021·2019-08-26 11:56
閱讀 1544·2019-08-23 16:20