在Vue的各个相关类中,Watcher类起到了中心枢纽的功能,响应式数据的变化通过dep通知到watcher,watcher通过其原型上的各个方法和回调函数实现computed、watch、渲染视图等功能。

var uid$2 = 0;

var Watcher = function Watcher (vm,expOrFn,cb,options) {
  // 与vue实例互相关联
  this.vm = vm;
  vm._watchers.push(this);

  if (options) {
    // deep参数为了监听更深层级值的变化
    this.deep = !!options.deep;
    // user参数表明cb是用户传入的,需要注意容错
    this.user = !!options.user;
    // lazy用在computed实现上,dep通知数据更新后仅仅标识当前数据是脏数据
    this.lazy = !!options.lazy;
    // sync参数表示dep通知数据更新后是否同步更新
    this.sync = !!options.sync;
  } else {
    this.deep = this.user = this.lazy = this.sync = false;
  }
  // 数据更新后要执行的回调
  this.cb = cb;
  // 唯一id
  this.id = ++uid$2;
  // 表示这个watcher是否还有用
  this.active = true;

  // 标记lazy watcher初始数据为脏
  this.dirty = this.lazy; 

  // 和dep相关联需要的几个属性
  this.deps = [];
  this.newDeps = [];
  this.depIds = new _Set();
  this.newDepIds = new _Set();

  // 调试时用到
  this.expression = expOrFn.toString();


  // getter是值获取函数
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = function () {};
      "development" !== 'production' && warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
  }
  // watcher的值
  this.value = this.lazy? undefined : this.get();
};

Dep.target = null;

var targetStack = [];
function pushTarget (_target) {
  if (Dep.target) { targetStack.push(Dep.target); }
  Dep.target = _target;
}
function popTarget () {
  Dep.target = targetStack.pop();
}

// 实现依赖收集,获得计算值
Watcher.prototype.get = function get () {
  // 将该watcher挂在Dep.target上
  pushTarget(this);
  var value;
  var vm = this.vm;
  // 因为是用户传进来的getter,容错
  if (this.user) {
    try {
      value = this.getter.call(vm, vm);
    } catch (e) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
    }
  } else {
    value = this.getter.call(vm, vm);
  }

  // deep所做的就是递归,让更深的dep关联到watcher
  if (this.deep) {
    traverse(value);
  }
  popTarget();

  // 完成依赖的更新
  this.cleanupDeps();
  return value
};

// 添加依赖
Watcher.prototype.addDep = function addDep (dep) {
  var id = dep.id;
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      dep.addSub(this);
    }
  }
};

// 解除不再需要的dep和watcher的关系
Watcher.prototype.cleanupDeps = function cleanupDeps () {
    var this$1 = this;

  // 消除不再需要的dep到watcher的关系
  var i = this.deps.length;
  while (i--) {
    var dep = this$1.deps[i];
    if (!this$1.newDepIds.has(dep.id)) {
      dep.removeSub(this$1);
    }
  }

  // 更新当前的依赖
  var tmp = this.depIds;
  this.depIds = this.newDepIds;
  this.newDepIds = tmp;
  this.newDepIds.clear();
  tmp = this.deps;
  this.deps = this.newDeps;
  this.newDeps = tmp;
  this.newDeps.length = 0;
};


// dep通知watcher时的入口函数
Watcher.prototype.update = function update () {
  // lazy watcher 仅仅表示数据是脏的,必要的时候通过evaluate获取数据
  if (this.lazy) {
    this.dirty = true;
  } 
  // 同步更新的watcher 同步调用回调
  else if (this.sync) {
    this.run();
  } 
  // 异步更新的watcher加入到更新队列
  else {
    queueWatcher(this);
  }
};

// 调用回调
Watcher.prototype.run = function run () {
  if (this.active) {
    var value = this.get();
    if (
      value !== this.value || isObject(value) || this.deep) {
      // 设定新值
      var oldValue = this.value;
      this.value = value;
      // 执行回调,还要容错
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue);
        } catch (e) {
          handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
        }
      } else {
        this.cb.call(this.vm, value, oldValue);
      }
    }
  }
};

// 用于lazy watcher
Watcher.prototype.evaluate = function evaluate () {
  this.value = this.get();
  this.dirty = false;
};

// watcher 嵌套引用时候需要
Watcher.prototype.depend = function depend () {
    var this$1 = this;

  var i = this.deps.length;
  while (i--) {
    this$1.deps[i].depend();
  }
};


// 解除和vue实例、dep实例的关系,标记为不再活动
Watcher.prototype.teardown = function teardown () {
  var this$1 = this;

  if (this.active) {
    // 解除vm对watcher的关系
    if (!this.vm._isBeingDestroyed) {
      remove(this.vm._watchers, this);
    }
    // 解除dep对watcher的依赖
    var i = this.deps.length;
    while (i--) {
      this$1.deps[i].removeSub(this$1);
    }
    this.active = false;
  }
};

Watcher原型链上的方法看起来比较多,我们可以先分下类:getaddDepcleanupDeps这三个方法是用来与dep互相关联使用的,getevaluate是用来获取watcher的值的,update是暴露出去的让dep通知的入口,run方法是执行watcher的回调的入口,最后一个teardown用来取消观察停止回调。

到这里我们已经掌握了依赖收集机制必要的知识,现在我们理一遍流程:当watcher要收集依赖时,它会调用get方法,把watcher本身挂载到Dep.target上,然后watcher调用getter函数,我们去获取响应式的属性,这样就走到了属性的get方法(defineReactive方法的结果),在属性的get方法中,dep调用depend方法,depend方法再调用watcher的addDep方法,这样watcher和dep就建立了联系,最后watcher调用cleanupDeps方法,把那些曾经关联到watchr但现在不再关联到watcher的dep的到watcher的关系取消掉。

之前一直没想明白为什么要用targetStack。考虑这么一个场景:computed属性A需要用到computed属性B,调用A对应watcher(watcherA)的get方法时,会先把A对应watcher先挂到Dep.target上,然后会调用watcherA的getter方法,在getterA的某个阶段会访问属性B,B的watcher也会调用对应的get方法,也需要收集依赖,这时我们需要暂存watcherA(入targetStack),然后把watcherB挂到Dep.target上。这种嵌套依赖需要targetStack辅助。还有一个问题,watcherB依赖的Dep实例,watcherA是否也需要依赖?答案是肯定的,毕竟watcherB不能把自身变化通知给watcherA,负责这个的是watcher原型链上的depend方法。在computed的具体实现中我们会看到它是如何运用的。

results matching ""

    No results matching ""