博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Angular 学习笔记:$digest 实现原理
阅读量:6256 次
发布时间:2019-06-22

本文共 5529 字,大约阅读时间需要 18 分钟。

$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://ulxsa.baihongyu.com/

你可能感兴趣的文章
buffer busy waits等待事件
查看>>
MySQL版本之分:Community Server、Embedded Server、Enterprise Server
查看>>
JVM及遗传算法,转摘牛人牛文
查看>>
C#用DataTable实现Group by数据统计
查看>>
iframe如何刷新的三种解决方案
查看>>
每日英语:Fewer Foreigners Eye US Graduate Science Programs
查看>>
Socket异步通信——使用IAsyncResult
查看>>
宋体、构造函数-浅出C++对象模型——理解构造函数、析构函数执行顺序-by小雨...
查看>>
我眼中的sencha touch(2013网页装在兜里)
查看>>
函数分组学通MongoDB——第三天 细说高级操作
查看>>
Windows程序设计_18_程序加载过程
查看>>
安装内容[Python]第三方库-Scrapy入门使用
查看>>
关闭web.config的继承
查看>>
一键让应用程序适配 iphone5
查看>>
http 长连接和轮询
查看>>
Windows CE 6.0的安装,简单定制和导出SDK--转载
查看>>
在Windows Server 2008 R2上安装Exchange 2013过程中遇到的一些问题
查看>>
Maven POM入门
查看>>
codeforces 6A. Triangle
查看>>
仿CSDN Blog返回页面顶部功能
查看>>