vue 响应式收集依赖、计算属性收集依赖源码解析

实例化响应式对象

// initData 
function initData(data) {
  ···
  observe(data, true)
}
// observe
function observe(value, asRootData) {
  ···
  ob = new Observer(value);
  return ob
}

// 观察者类,对象只要设置成拥有观察属性,则对象下的所有属性都会重写getter和setter方法,而getter,setting方法会进行依赖的收集和派发更新
var Observer = function Observer (value) {
    ···
    // 将__ob__属性设置成不可枚举属性。外部无法通过遍历获取。
    def(value, '__ob__', this);
    // 数组处理
    if (Array.isArray(value)) {
        ···
    } else {
      // 对象处理
      this.walk(value);
    }
  };

Observer.prototype.walk = function walk (obj) {
    // 获取对象所有属性,遍历调用defineReactive###1进行改写
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
        defineReactive###1(obj, keys[i]);
    }
};


function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable, // 是否可枚举
    writable: true,
    configurable: true
  });
}

defineReactive

function defineReactive###1 (obj,key,val,customSetter,shallow) {
    // 每个数据实例化一个Dep类,创建一个依赖的管理
    var dep = new Dep();

    var property = Object.getOwnPropertyDescriptor(obj, key);
    // 属性必须满足可配置
    if (property && property.configurable === false) {
      return
    }
    // cater for pre-defined getter/setters
    var getter = property && property.get;
    var setter = property && property.set;
    // 这一部分的逻辑是针对深层次的对象,如果对象的属性是一个对象,则会递归调用实例化Observe类,让其属性值也转换为响应式对象
    //返回对象的observer实例
    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,s
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val;
        if (Dep.target) {
          // 为当前watcher添加dep数据
          dep.depend();
          //observer实例也添加该watcher
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
              dependArray(value);
            }
          }
        }
        return value
      },
	  set: function reactiveSetter (newVal) {
	      var value = getter ? getter.call(obj) : val;
	      // 新值和旧值相等时,跳出操作
	      if (newVal === value || (newVal !== newVal && value !== value)) {
	        return
	      }
	      ···
	      // 新值为对象时,会为新对象进行依赖收集过程
	      childOb = !shallow && observe(newVal);
	      dep.notify();
	    }

    });
  }

Dep

src/core/observer/dep.js

let uid = 0
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;
  constructor () {
    this.id = uid++
    this.subs = []
  }
  // 添加观察者
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  // 移除观察者
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
  depend () {
    if (Dep.target) { //Dep.target为当前组件对应的Watcher的实例
      // 调用 Watcher 实例的 addDep 函数,将当前Dep实例传进去,然后调用实例的addSub方法添加wacher实例
      Dep.target.addDep(this)
    }
  }
  // 派发更新
  notify () {
    var subs = this.subs.slice();
    if (!config.async) {
      // 根据依赖的id进行排序
      subs.sort(function (a, b) { return a.id - b.id; });
    }
    for (var i = 0, l = subs.length; i < l; i++) {
      // 遍历每个依赖,进行更新数据操作。
      subs[i].update();
    }
  }
}


Watcher

var watcher = function Watcher(
  vm, // 组件实例
  expOrFn, // 执行函数
  cb, // 回调
  options, // 配置
  isRenderWatcher // 是否为渲染watcher
) {
  this.vm = vm;
    if (isRenderWatcher) {
      vm._watcher = this;
    }
    vm._watchers.push(this);
    // options
    if (options) {
      this.deep = !!options.deep;
      this.user = !!options.user;
      this.lazy = !!options.lazy;
      this.sync = !!options.sync;
      this.before = options.before;
    } else {
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.cb = cb;
    this.id = ++uid$2; // uid for batching
    this.active = true;
    this.dirty = this.lazy; // for lazy watchers
    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();
    this.expression = expOrFn.toString();
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = parsePath(expOrFn);
      if (!this.getter) {
        this.getter = noop;
        warn(
          "Failed watching path: \"" + expOrFn + "\" " +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        );
      }
    }
    // lazy为计算属性标志,当watcher为计算watcher时,不会立即执行get方法进行求值
    this.value = this.lazy
      ? undefined
      : this.get();
  
}

Watcher.prototype.get = function get () {
	//将当前watcher放在Dep.target
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } catch (e) {
     ···
    } finally {
      ···
      //把当前Dep.target的watcher弹出,恢复到上一个watcher,依赖收集过程完成
      popTarget();
      this.cleanupDeps();
    }
    return value
  };


Watcher.prototype.addDep = function addDep (dep) {
    var id = dep.id;
    if (!this.newDepIds.has(id)) {
      // newDepIds和newDeps记录watcher拥有的数据
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      // 避免重复添加同一个data收集器
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  };
Watcher.prototype.update = function update () {
  // 计算属性分支  
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};

//计算属性重新调用计算的方法
 Watcher.prototype.evaluate = function evaluate () {
    // 对于计算属性而言 evaluate的作用是执行计算回调
    this.value = this.get();
    this.dirty = false;
  };

//非计算属性调用回调更改的方法
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);
        }
      }
    }
  };

渲染watcher

new Watcher(vm, updateComponent, noop, {
  before: function before () {
    if (vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'beforeUpdate');
    }
  }
}, true /* isRenderWatcher */);

计算属性watcher

  • omputed的初始化过程,会遍历computed的每一个属性值,并为每一个属性实例化一个computed watcher
  • 其中{ lazy: true}是computed watcher的标志
  • 模板在的计算watcher需要收集渲染watcher
function initComputed() {
  ···
  for(var key in computed) {
    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
  }
  if (!(key in vm)) {
    defineComputed(vm, key, userDef);
  }
}

// computed watcher的标志,lazy属性为true
var computedWatcherOptions = { lazy: true };

defineComputed

function defineComputed (target,key,userDef) {
  // 非服务端渲染会对getter进行缓存
  var shouldCache = !isServerRendering();
  if (typeof userDef === 'function') {
    // 
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef);
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }
  if (sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        ("Computed property \"" + key + "\" was assigned to but it has no setter."),
        this
      );
    };
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

createComputedGetter

function createComputedGetter (key) {
    return function computedGetter () {
      var watcher = this._computedWatchers && this._computedWatchers[key];
      if (watcher) {
      	//当dirty为true时才会重新计算,首次数据的数据改变后,调用watcher的update会将dirtry置为true
      	//数据改变后触发渲染watcher,此时又会访问到计算属性的getter,从而再次触发该方法,又因为dirty为true,执行evaluate()进行重新计算
        if (watcher.dirty) {
          watcher.evaluate();
        }
        //当模板挂载|重新渲染时,模板中有计算属性时,此时访问到模板的getter,因为还在渲染过程中,所以Dep.target为渲染watcher
        //将渲染watcher添加进计算属性的watcher中,使得当模板只有计算属性,且依赖的响应式对象改变时(若响应式对象未在模板中,因为未在模板中触发getter,所以dep中没有渲染watcher,只有计算属性的watcher),更新值也能触发重新渲染
        if (Dep.target) {
          watcher.depend();
        }
        return watcher.value
      }
    }
  }

收集流程分析
非计算属性收集watcher

  • 实例挂载前会创建一个渲染watcher
  • updateComponent的逻辑会执行实例的挂载,在这个过程中,模板会被优先解析为render函数,而render函数转换成Vnode时,会访问到定义的data数据,这个时候会触发gettter进行依赖收集。
  • 因为初始化非计算属性watcher时,会调用watcher的get方法,将Dep.target变成该渲染watcher,回调为updateComponent,所以根据上一步会访问到数据的getter,从而每个数据的dep中都能收集到该渲染watcher
new Watcher(vm, updateComponent, noop, {
  before: function before () {
    if (vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'beforeUpdate');
    }
  }
}, true /* isRenderWatcher */);

遇到计算属性收集watcher

  • 当模板中有计算属性时,计算属性初始计算时会访问到依赖的响应式属性,从而触发其中的getter,从而将计算属性的watcher收集到dep中,所以每次依赖的属性改变能触发计算属性的watcher
  • 当计算属性依赖的响应式属性改变时,会遍历调用watcher的update,从而将watcher的dirty置为true,然后触发渲染watcher,在重新渲染时又会访问到计算watcher的getter,此时dirty为true,从而触发重新计算
  • 当计算属性在模板中,但依赖的响应式属性为在模板中,因为未在模板中触发getter,所以响应式属性dep中没有渲染watcher,此时需要为watcher添加渲染watcher,因为在模板渲染触发各个属性的getter时,Dep.target就为渲染watcher,所以直接添加即可
function createComputedGetter (key) {
    return function computedGetter () {
      var watcher = this._computedWatchers && this._computedWatchers[key];
      if (watcher) {
      	//当dirty为true时才会重新计算,首次数据的数据改变后,调用watcher的update会将dirtry置为true
      	//数据改变后触发渲染watcher,此时又会访问到计算属性的getter,从而再次触发该方法,又因为dirty为true,执行evaluate()进行重新计算
        if (watcher.dirty) {
          watcher.evaluate();
        }
        //当模板挂载|重新渲染时,模板中有计算属性时,此时访问到模板的getter,因为还在渲染过程中,所以Dep.target为渲染watcher
        //将渲染watcher添加进计算属性的watcher中,使得当模板只有计算属性,且依赖的响应式对象改变时(若响应式对象未在模板中,因为未在模板中触发getter,所以dep中没有渲染watcher,只有计算属性的watcher),更新值也能触发重新渲染
        if (Dep.target) {
          watcher.depend();
        }
        return watcher.value
      }
    }
  }

模板中的变量都能收集到渲染watcher的原因

  • 在触发渲染watcher时,调用watcher.get方法,调用pushTarget将Dep.target置为渲染watcher,然后执行回调,在转换成虚拟node过程中会访问每一个变量的getter从而收集到渲染watcher,最后调用popTarget将渲染watcher弹出,恢复队列中的上一个watcher
new Watcher(vm, updateComponent, noop, {
  before: function before () {
    if (vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'beforeUpdate');
    }
  }
}, true /* isRenderWatcher */);

派发watcher更新过程

Dep.prototype.notify = function notify () {
    var subs = this.subs.slice();
    if (!config.async) {
      // 根据依赖的id进行排序
      subs.sort(function (a, b) { return a.id - b.id; });
    }
    for (var i = 0, l = subs.length; i < l; i++) {
      // 遍历每个依赖,进行更新数据操作。
      subs[i].update();
    }
  };

 Watcher.prototype.update = function update () {
    ···
    queueWatcher(this);
  };

queueWatcher

  • 会将数据所收集的依赖依次推到queue数组中,数组会在下一个事件循环’tick’中根据缓冲结果进行视图更新
  • 在执行视图更新过程中,难免会因为数据的改变而在渲染模板上添加新的依赖,这样又会执行queueWatcher的过程。所以需要有一个标志位来记录是否处于异步更新过程的队列中
  • flushing标识是否在异步更新过程中
function queueWatcher (watcher) {
    var id = watcher.id;
    // 保证同一个watcher只执行一次
    if (has[id] == null) {
      has[id] = true;
      //当此时未在更新过程中时,将watcher存入queue
      if (!flushing) {
        queue.push(watcher);
      } else {
      //当正在更新过程中时,根据watcher.id属性找到应该放置的位置然后插入到该位置
        var i = queue.length - 1;
        while (i > index && queue[i].id > watcher.id) {
          i--;
        }
        queue.splice(i + 1, 0, watcher);
      }
      ···
      //优先使用微任务进行更新
      nextTick(flushSchedulerQueue);
    }
  }

根据id排序的原因

  • 组件创建是先父后子,所以组件的更新也是先父后子,因此需要保证父的渲染watcher优先于子的渲染watcher更新。
  • 用户自定义的watcher,称为user watcher。 user watcher和render watcher执行也有先后,由于user watchers比render watcher要先创建,所以user watcher要优先执行。
  • 如果一个组件在父组件的 watcher 执行阶段被销毁,那么它对应的 watcher 执行都可以被跳过。

flushSchedulerQueue

  • 非计算属性调用wacher.run的方法进行重新计算,然后重置队列,调用更新的钩子等操作
function flushSchedulerQueue () {
    currentFlushTimestamp = getNow();
    flushing = true;
    var watcher, id;
    // 对queue的watcher进行排序
    queue.sort(function (a, b) { return a.id - b.id; });
    // 循环执行queue.length,为了确保由于渲染时添加新的依赖导致queue的长度不断改变。
    for (index = 0; index < queue.length; index++) {
      watcher = queue[index];
      // 如果watcher定义了before的配置,则优先执行before方法
      if (watcher.before) {
        watcher.before();
      }
      id = watcher.id;
      has[id] = null;
      watcher.run();
      // in dev build, check and stop circular updates.
      if (has[id] != null) {
        circular[id] = (circular[id] || 0) + 1;
        if (circular[id] > MAX_UPDATE_COUNT) {
          warn(
            'You may have an infinite update loop ' + (
              watcher.user
                ? ("in watcher with expression \"" + (watcher.expression) + "\"")
                : "in a component render function."
            ),
            watcher.vm
          );
          break
        }
      }
    }

    // keep copies of post queues before resetting state
    var activatedQueue = activatedChildren.slice();
    var updatedQueue = queue.slice();
    // 重置恢复状态,清空队列
    resetSchedulerState();

    // 视图改变后,调用其他钩子
    callActivatedHooks(activatedQueue);
    callUpdatedHooks(updatedQueue);

    // devtool hook
    /* istanbul ignore if */
    if (devtools && config.devtools) {
      devtools.emit('flush');
    }
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值