Vue2收集依赖的时机以及生命周期解读

生命周期大家肯定都是有所解的,所以在此处就不在过多赘述。

首先来看一下Vue源码的 _init 函数都干了些什么

Vue.prototype._init = function (options?: Object) {
  // ...
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')
  if (vm.$options.el) {
	   vm.$mount(vm.$options.el) 
  }
  // ...
}

结合生命周期的图解会更容易理解。

首先在beforeCreate之前会初始化事件和生命周期钩子。事件指的是父组件绑定到该组件身上的事件。在此处没有初始化数据,所以一般不进行发送请求的操作。

其次在created之前,这个时候会初始化props,computed,data,methods,watch,这个过程数据已经变为了响应式数据。此处可以进行异步请求,但是修改数据并不会触发beforeUpdate。显然,beforeCreate的钩子函数当中就不能获取到props,data当中的值,也不能调用methods当中的方法。

接着是beforeMount 和 mounted

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // ...
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

在执行_render渲染vnode之前,执行beforeMount,执行完成后把vnode映射到真实dom执行mounted钩子,如果为null表示这是通过new Vue初始化的过程,不为null表示组件初始化的过程。

  export default class Watcher {
  // ...省略
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value

  }

在watcher构造器当中,判断回调函数cb是否是一个函数,如果是函数赋值给this.getter() 然后调用this.get方法

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

首先将这个watcher实例入栈,设置Dep.target = watcher实例,也就是启用依赖收集,然后调用this.getter(),也就是updateComponent这个回调函数。执行_render把vnode映射到真实dom,在这个方法中会访问到所依赖的数据,触发getter,然后判断Dep.target是否存在,我们在pushTarget中已经启用了依赖收集,所以这个时候就会通过判断,执行depend方法,调用Watcher的addDep方法,在addDep方法中,首先获取dep的id,然后判断newDepIds数组中是否存在这个id,防止重复收集依赖。

addDep (dep: Dep) {
  const 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
var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
};
Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

到这里,依赖收集就已经结束了。当数据更新之后会执行beforeUpdate,然后使用diff进行虚拟dom的对比(这次的vnode与上次的vnode),然后重新渲染,当更新完成后,执行updated钩子,数据已经更新,dom也重新render完成,此时可以操作更新之后的vnode了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值