为什么要依赖收集

先看下面这段代码

  1. new Vue({
  2. template:
  3. `<div>
  4. <span>text1:</span> {{text1}}
  5. <span>text2:</span> {{text2}}
  6. <div>`,
  7. data: {
  8. text1: 'text1',
  9. text2: 'text2',
  10. text3: 'text3'
  11. }
  12. });

按照之前《响应式原理》中的方法进行绑定则会出现一个问题——text3在实际模板中并没有被用到,然而当text3的数据被修改(this.text3 = ‘test’)的时候,同样会触发text3的setter导致重新执行渲染,这显然不正确。

先说说Dep

当对data上的对象进行修改值的时候会触发它的setter,那么取值的时候自然就会触发getter事件,所以我们只要在最开始进行一次render,那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。

定义一个依赖收集类Dep。

  1. class Dep {
  2. constructor () {
  3. this.subs = [];
  4. }
  5. addSub (sub: Watcher) {
  6. this.subs.push(sub)
  7. }
  8. removeSub (sub: Watcher) {
  9. remove(this.subs, sub)
  10. }
  11. /*Github:https://github.com/answershuto*/
  12. notify () {
  13. // stabilize the subscriber list first
  14. const subs = this.subs.slice()
  15. for (let i = 0, l = subs.length; i < l; i++) {
  16. subs[i].update()
  17. }
  18. }
  19. }
  20. function remove (arr, item) {
  21. if (arr.length) {
  22. const index = arr.indexOf(item)
  23. if (index > -1) {
  24. return arr.splice(index, 1)
  25. }
  26. }
  27. }

Watcher

订阅者,当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触发dep对象的notify,通知所有Watcher对象去修改对应视图。

  1. class Watcher {
  2. constructor (vm, expOrFn, cb, options) {
  3. this.cb = cb;
  4. this.vm = vm;
  5. /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/
  6. Dep.target = this;
  7. /*Github:https://github.com/answershuto*/
  8. /*触发渲染操作进行依赖收集*/
  9. this.cb.call(this.vm);
  10. }
  11. update () {
  12. this.cb.call(this.vm);
  13. }
  14. }

开始依赖收集

  1. class Vue {
  2. constructor(options) {
  3. this._data = options.data;
  4. observer(this._data, options.render);
  5. let watcher = new Watcher(this, );
  6. }
  7. }
  8. function defineReactive (obj, key, val, cb) {
  9. /*在闭包内存储一个Dep对象*/
  10. const dep = new Dep();
  11. Object.defineProperty(obj, key, {
  12. enumerable: true,
  13. configurable: true,
  14. get: ()=>{
  15. if (Dep.target) {
  16. /*Watcher对象存在全局的Dep.target中*/
  17. dep.addSub(Dep.target);
  18. }
  19. },
  20. set:newVal=> {
  21. /*只有之前addSub中的函数才会触发*/
  22. dep.notify();
  23. }
  24. })
  25. }
  26. Dep.target = null;

将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改触发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。