Scope object

Scope对象其实就是一个简单的POJO(plain old JavaScript Object)。我们可以给它任意的添加属性。

// scope.js
export default class Scope {

}
// test.js
const scope = new Scope();
scope.aProperty = 1;
expect(scope.aProperty).toBe(1);

$watch$digest

$watch$digest就像一个硬币的两面。他们组合在一起就是脏检查循环的核心:对于数据变化的响应。

$watch:

$watch(watchExpression, listener, [objectEquality]);
  • watchExpression: 监听的数据
  • listener:数据发生变化的时候调用
  • objectEquality: 后面单独说

angularjs中将所有的 watchExpression 存放到一个叫作$$watcher的数组中,因此我们创建一个数组:

$$watchers = [];

$watch(watchFn, listenerFn) {

    const watcher = {
      watchFn,
      listenerFn
    };
    
    this.$$watchers.push(watcher);
} 

$digest:

它遍历scope上的所有watchers,计算 watchExpression ,并且调用它对应的 listenerFn。

_.forEach(this.$$watchers, watcher => {
  watcher.listenerFn();
});

脏值检测

目的:只有监控的值发生改变的时候我们才执行对应的listener。
思路:存储上一次的值,和这一次值的进行比对。

$digest() {
    // 将变量声明提取到循环外面
    let newValue;
    let oldValue;
    
    _.forEach(this.$$watchers, watcher => {
      
      newValue = watcher.watchFn(this);
      // 第一次获取last的时候值为undefined
      oldValue = watcher.last;
      // 只有当新旧值不相等的时候才执行listener
      if (newValue !== oldValue) {
        watcher.last = newValue;
        watcher.listenerFn(newValue, oldValue, this);
      }
    });
}

初始化watch的值

angularjs中的初始化值为一个函数:

function initWatchVal() {}

然后将其赋值给watch的last:

const watcher = {
  watchFn,
  listenerFn,
  last: initWacthVal
};

持续监测

添加一个帮助方法,将所有的watchFn运行一次,返回一个boolean值,表示是否有更新:

$digest() {
  
    let newValue;
    let oldValue;
    // 标记是否为脏
    let dirty;
    // 上来先执行一次看是否所有值发生变化,如果有变化,则第二次执行watch
    do {
      // 初次进来设置为false
      dirty = false;
    
      _.forEach(this.$$watchers, watcher => {
      
        newValue = watcher.watchFn(this);
        // 第一次获取last的时候值为undefined
        oldValue = watcher.last;
        // 只有当新旧值不相等的时候才执行listener
        if (newValue !== oldValue) {
          dirty = true;
          watcher.last = newValue;
          // watcher.listenerFn(newValue, oldValue, this);
          const temp = oldValue === initWacthVal ? newValue : oldValue;
          watcher.listenerFn(newValue, temp, this);
        }
      });
    
    } while (dirty);
    
}

ttl

如果watch一直为不稳定的值,我们需要停止脏检查。angularjs中默认的ttl为10,对外暴露可修改。

// 如果为脏,并且ttl达到0的时候
if (dirty && !(ttl--)) {
    throw '10 digest iterations reached';
}

停止脏检查

添加lastDirtyWatch去判断,看源码。

  • 执行$digest的时候将 lastDirtyWatch = null
  • 当前watcer 和 lastDirtyWatch 相同的时候设置为 null
  • watch的listener里面包含有watch的时候,重置为 null

基于值的脏检查

$watch(watchExpression, listener, [objectEquality]);

判断是否相等要包含数组和对象,angularjs内置的方法为:

angular.equals(o1, o2);

然后替换原来的新旧值的判断:

// 判断是否相等
function areEqual(newValue, oldValue, valueEq) {
  
  if (valueEq) {
    return _.isEqual(newValue, oldValue);
  } else {
    return newValue === oldValue;
  }

}

销毁监听

angularjs中的销毁是给$watch的时候返回一个方法,当你调用的时候,直接将它从数组中移除。

$watch(watchFn, listenerFn, valueEq) {

    const watcher = {
      watchFn,
      listenerFn,
      valueEq, 
      last: initWacthVal
    };
    
    this.$$watchers.push(watcher);
    // 新加入一个watcher的时候将lastDirtyWatch初始化
    this.lastDirtyWatch = null;

    return () => {
      const index = _.find(this.$$watchers, watcher);
      if (index >= 0) {
        this.$$watchers.splice(index, 1);
      }
    };
}
对内简单的分享,记录下来。参照书名:build your own angularjs

silence
386 声望26 粉丝

我就是我,是不想绽放的烟火!