Vue响应式原理Observer

组件初始化时会执行initState方法,主要是对 props、methods、data、computed、watcher 等属性做了初始化操作。这里我们分析 data

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

 

 

  • initData

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

initData主要做了两件事:

1、调用了proxy  把data的数据代理代理vue实例上。

2、调用observer把数据变成响应式。我们重点分析observer

 

  • observer && Observer

export function observe (value: any, asRootData: ?boolean): Observer | void {
  //......此处省去无关紧要代码
  ob = new Observer(value)
  return ob
}
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number;

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      //......实现数组响应式 后面的文章有单独的分析
    } else {
      this.walk(value)
    }
  }

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
}

Observer主要做了三件事:

1、给对象添加一个__ob__的属性;

2、给对象添加一个dep 依赖收集器;

3、循环对象,调用defineReactive,给对象每个属性值添加响应式

 

  • defineReactive

export const defineReactive = (
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function,
  shallow?: boolean
) => {
  const dep = new Dep()
  //......此处省略一些次要代码

  let childOb = !shallow && observer(val)

  Object.defineProperty(obj, key, {
    get() {
      if(Dep.target) {
        dep.depend()
        if(childOb) {
          childOb.dep.depend()
        }
      }
      return val
    },
    set(newVal) {
      if(val !== newVal) {
        val = newVal
        childOb = observer(newVal)
        dep.notify()
      }
    }
  })
}

defineReactive给对象的属性实现响应式:

1、给对象的属性创建一个Dep依赖收集器

2、执行observer(val),因为可能是嵌套对象

3、定义get方法,当对象的该属性被访问时,执行dep.depend收集器dep会收集Watcher对象(观察者)。那什么时候会执行get方法,Watcher对象又是谁呢?

  • Dep是一个Class,它有一个静态属性 target,这是一个全局的Watcher,同一个时间只能有一个全局的Watcher被计算
  • Vue的观察者Watcher分为三类。第一种是渲染Watcher,用来观察数据变化后重新渲染dom的;第二种是computed Watcher;第三种是自定义Watcher
  • 当Vue组件初始化computed计算属性时,计算属性会使用data的值,那么get方法就会被执行,此时的Watcher对象就是computed Watcher
  • 当Vue组件初始化自定义watch的时候,我们用watch去监听data的变化,此时该data的get方法会被执行,此时的Watcher对象就是自定义的Watcher
  • 当Vue组件执行mount挂载组件的时候,会去生成VNode,VNode会访问到data,然后get方法会被执行,此时Watcher对象就是渲染Watcher

4、定义set方法,当数据发生改变时,执行dep.notify派发通知,get方法收集的Watcher接到通知后执行对应的操作,如果是渲染Watcher则执行更新dom的操作。新的数据可能也是个对象·,所以需要执行observer(newVal),给新值添加响应式

5、大家可能有个疑问,childOb.dep.depend()这里的作用。childOb是一个Observer对象,我们上面分析Observer的时候,Observer对象里也有一个dep收集器,Observer会执行this.dep=new Dep(),并且循环对象调用defineReative。defineReative已经给对象的属性添加了dep,那Observer对象里的this.dep有什么用呢

 

上一篇:数据可视化 - 饼图集


下一篇:数据可视化 - 关系图