Angular 学习笔记:$digest 实现原理
$watch 和 $digest
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 = wather.last; if (newValue !== oldValue) { watch.last = newValue; watcher.listenerFn(newValue,oldValue,self); } }); } 上述代码在大部分情况下可以正常运行,但是当我们首次遍历 watcher 对象时其 console.log(undefined === undefined) // true 我们使用 function initWatchVal() { // TODO } Scope.prototye.$watch = function(watchFn,listenerFn: listenerFn || function() {},last: initWatchVal }; this.$$watchers.push(watcher); } Scope.prototype.$digest = function() { let self = this; let newValue,oldValue === initWatchVal ? newValue : oldValue,self); } }); } 循环进行脏检查在进行 digest 时往往会发生如下情况,即某个 watcher 执行 listener 方法会引起其他 watcher 监听的对象数据发生改变,因此我们需要循环进行脏检查来使变化“彻底”完成。 Scope.prototype.$$digestOnce = function() { let self = this; let newValue,dirty; this.watchers.forEach((watcher) => { newValue = watcher.watchFn(self); oldValue = wather.last; if (newValue !== oldValue) { dirty = true; watch.last = newValue; watcher.listenerFn(newValue,self); } }); return dirty; } Scope.prototype.$digest = function() { let dirty; do { dirty = this.$$digestOnce(); } while (dirty); } 上述代码只要在遍历中发现脏值,就会多循环一轮直到没有发现脏值为止,我们考虑这样的情况:即是两个 watcher 之间互相影响彼此,则会导致无限循环的问题。 我们使用 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 进行检查,我们通过使用 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,dirty; this.watchers.forEach((watcher) => { newValue = watcher.watchFn(self); oldValue = wather.last; if (newValue !== oldValue) { self.$$lastDirtyWatch = watcher; dirty = true; watch.last = newValue; watcher.listenerFn(newValue,self); } else if (self.$$lastDirtyWatch === watcher) { return false; } }); return dirty; } 同时为了避免 $watch 嵌套使用带来的不良影响,我们需要在每次添加 watcher 时重置 $$lastDirtyWatch: Scope.prototye.$watch = function(watchFn,last: initWatchVal }; this.$$watchers.push(watcher); this.$$lastDirtyWatch = null; } 深浅脏检查目前为止我们实现的脏检查,仅能监听到值的变化(浅脏检查),无法判断引用内部数据发生的变化(深脏检查)。 Scope.prototye.$watch = function(watchFn,listenerFn,valueEq) { let watcher = { watchFn: watchFn,valueEq: !!valueEq,last: initWatchVal }; this.$$watchers.push(watcher); this.$$lastDirtyWatch = null; } Scope.prototype.$$areEqual = function(newValue,valueEq) { if (valueEq) { return _.isEqual(newValue,oldValue); } else { return newValue === oldValue; } } Scope.prototype.$$digestOnce = function() { let self = this; let newValue,dirty; this.watchers.forEach((watcher) => { newValue = watcher.watchFn(self); oldValue = wather.last; if (!self.$$areEqual(newValue,watcher.valueEq)) { self.$$lastDirtyWatch = watcher; dirty = true; watch.last = watcher.valueEq ? _.cloneDeep(newValue) : newValue; watcher.listenerFn(newValue,self); } else if (self.$$lastDirtyWatch === watcher) { return false; } }); return dirty; } NaN 的兼容考虑需要注意的是,NaN 不等于其自身,所以在判断 newValue 与 oldValue 是否相等时,需要特别考虑。 Scope.prototype.$$areEqual = function(newValue,oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |