1

前言

读这篇文章前,最好是先读vue数据绑定源码,因为本篇是接这章写的,放在一篇文章里,篇幅太大,我只好分成两章了。

初始化vue实例

Vue.prototype._init = function (options) {
    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) // 挂载组件
    }
}

// mountComponent是在挂载组件时调用的方法
export function mountComponent (vm, el ,hydrating) {
  callHook(vm, 'beforeMount')

  let updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }

  new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
  
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  
  return vm
}

initState

上一篇文章,我们已经了解了ObserverDepWatcher都是负责什么?如何互相协作?
接下来,我们从数据的入口开始,了解vue是如何使用这几个类完成的数据驱动视图更新的?
初始化state,就是初始化这几个属性。

clipboard.png

先讲下什么是可观察者对象呢?
具备两个条件:
1、取值的时候,能把要取值的watcher(观察者对象)加入它的dep(依赖,也可叫观察者管理器)管理的subs列表里(即观察者列表);
2、设置值的时候,有了变化,所有依赖于它的对象(即它的dep里收集到的观察者watcher)都得到通知。
watcher里面存储它都观察了谁(dep),dep里面存储了都谁观察了自己。

initProps

clipboard.png

循环每个props属性,对每个属性调用defineReactive,把每个属性加上getset装饰器,变为可观察者对象,如果属性值是对象也会递归转化。

initData

clipboard.png

observe就是循环把data中的所有项都转换成可观察者对象,如果子项是对象或数组就递归转化,确保data里的每一项及其后代都转化成了可观察者对象
初始化数据时,不管data里的数据渲染用没用到,都会转成可观察者对象。
对于propsdata,只有哪个watcher用到了去读取时,才会把该watcher加到他的观察者列表中。

dataprops里都调用了proxy这个方法,他是做什么的呢?
proxy(vm,’_data’,key)
proxy就是把dataprops下的属性都代理到了vm实例下,vm._data.a等价于vm.a
原理就是Object.definePropertyvmkey属性设置getset方法,当访问get的时候,返回的是vm._data[key];当访问set的时候,设置的是vm._data[key]的值。

initComputed

clipboard.png

初始化计算属性,就是为每一个计算属性定义一个Watcher观察者对象。这个对象是lazy的,不会立即就去执行计算(即get方法),等到用的时候才会去计算,这个时候就会去读取这个计算属性依赖的可观察属性的值来计算,读取的时候就会把这些依赖添加进这个计算watcher里,同时这些依赖的订阅者列表也会加入这个计算watcher。所以当依赖变化时,通知到他的所有订阅watcher。计算watcher接到依赖发生变化了,不会立即计算新值,而是标记dirtytrue,读取这个计算属性的时候,发现dirtytrue,就是说数据已经不是最新的了,需要重新计算,然后才去计算,否则直接取上一次计算的值value

如果某个data通过计算属性间接的被用到了渲染里,那么这个data也会被加入到渲染watcher的依赖列表,它的订阅者列表也会保存渲染watcher
只有当模版里使用了该计算属性,这个计算属性依赖的可观察者才会被加入到渲染watcher的依赖列表。如果模版里没有直接或间接用到可观察者对象属性,那么当你set它的时候,也就不会触发更新操作。

initWatch

clipboard.png

初始化watch,就是为每个watch属性创建一个观察者对象,这个expOrFn解析取值表达式去取值,然后就会调用相关data/prop属性的get方法,get方法又会在他的观察者列表里加上该watcher,一旦这些依赖属性值变化就会通知该watcher执行update方法。即会执行他的回调方法cb,也就是watch属性的handler方法。

到这里,数据的初始化就完成了。

mountComponent

clipboard.png

这个方法是在,所有的数据初始化完成后,执行挂载组件时调用,创建一个渲染watcher,每个组件有且仅有一个渲染watcherupdateComponentwatchergetter属性,创建后,立即调用get方法,即是调用updateComponent,也就是调用render方法,render里使用了的数据就会读取相关的dataget方法,就会把data的依赖加进这个渲染watcher的依赖列表里,datasubs也会加入渲染watcher,如此,当设置data时拦截的set方法就会通知渲染watcher调用update方法。

我总结下:
一个数据变更后,通知他的Watcher去执行update。
不同类型的Watcher职责不同,vue里的Watcher可以分为3类:
渲染Watcher、计算Watcher、侦听器Watcher,注意这3中Watcher的getter属性分别是什么。

  • 渲染Watcher的update,负责重新渲染,执行render;getter是updateComponent。
  • 计算Watcher的update,负责标记dirty,告诉它数据不是最新的需要重新计算了;getter是计算属性的get方法。
  • 侦听器Watcher的update,负责执行回调方法,也就是watch的handler;getter是watch的取值表达式。

陈凤娟
171 声望17 粉丝

一个追求专业与卓越的前端开发工程师