# state

上一章遗留的问题updateComponent为什么放到新建的渲染Watcher执行,这是因为vue是利用数据响应式来实现数据驱动的,updateComponent是渲染组件的方法,首次加载或者数据变化都应该执行updateComponent完成组件的更新。为什么数据变化就可以自动执行updateComponent更新组件,这就是vue的响应式系统要做的事。

# 观察者模式

vue响应式系统使用的是观察者模式,观察者模式与发布订阅模式很像,都需要依赖收集和事件发布。从一个例子了解vue的观察者模式。

宿舍住了一群人想装修宿舍,他们每个人都有一个电话本,上面可以记录装修师傅的电话。如果有人想粉刷墙,他得先在自己的电话本记录刷墙师傅的电话,然后才能联系师傅来刷墙,如果有人想装地板,就得记录装地板师傅的电话再联系。

vue的data数据就表示宿舍的舍员,他们的电话本就是后面要讲的Dep,用来收集依赖的容器,电话本收集的其实是装修师傅们的电话,需要刷墙就得收集刷墙师傅的电话。每一位装修师傅表示的就是后面要讲的Watcher,他具有某项技能负责具体的施工。所以如果哪位舍员想装修房子,只需要打开自己的电话本联系自己收集的装修师傅就可以了,联系师傅这一步骤就叫做事件发布。

# 添加state逻辑

本小节提供了在缩影版基础上添加data响应式和动态绑定功能,切换到state分支就可以查看完整代码 (opens new window)。下面开始分析这个版本增加了哪些代码。

打开mysrc/core/instance/index.js,添加stateMixin方法

function Vue (options) {
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue

stateMixin在mysrc/core/instance/state.js中定义,查看stateMixin方法的实现

export function stateMixin (Vue) {

  const dataDef = {}
  dataDef.get = function () { return this._data }

  Object.defineProperty(Vue.prototype, '$data', dataDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

}

stateMixin方法主要对Vue.prototype添加了$data属性、$set方法、$delete方法和$watch方法。$data属性的get方法返回的就是用户再data中返回的数据对象,暴露给用户使用vm.$data就可以获取Vue 实例观察的数据对象。因为 Vue 无法探测普通的新增或删除 property (比如 this.myObject.newProperty = 'hi'),所以内置了$set和$delete方法,使新增的属性变成响应式的,他俩的实现这里不展开讲后面再了解。

data变成响应式的过程是在_init方法中调用的initState实现的

Vue.prototype._init = function (options) {
  ...
  vm._renderProxy = vm
  vm._self = vm
  initRender(vm)
  initState(vm)
  ...
}

initState定义在mysrc/core/instance/state.js中

export function initState (vm) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
}

当用户写了data时,使用initData方法处理:

function initData (vm) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 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 */)
}

因为vue建议data写成函数形式同时也支持直接把data写成对象,先判断如果是函数类型通过getData获取函数返回值,如果data是对象类型直接返回data,接着对data中的属性与props或者methods重名的判断,并在开发环境中警告提示,然后通过proxy把data返回的属性代理到vm实例上,也就是把data数据声明到vm实例,这也就是为什么直接使用this.xxx就能访问到data数据的原因。最后执行observe方法。

总结:到此为止state版本就已经为实现数据的响应式做好了准备,下面就开始重点分析Observe到底是怎么实现响应式的。