Vue2.0-data数据响应(源码解析)

data数据绑定-观察者模式

在new Vue()时,会触发vue中的_init方法,然后在initState中操作data

initState

export function initState(vm: Component) {
    //初始化watchers观察者数组
    vm._watchers = []
    ........
    if (opts.data) {
        initData(vm)
    }
    .......
}

调用initData开始初始化data对象

initData

function initData(vm: Component) {
    //获取data
  let data = vm.$options.data

    //判断data是函数还是对象
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}

  // 获取data的key
  const keys = Object.keys(data)
    ....和prop,methods进行name查重操作
  // observe data
  observe(data, true /* asRootData */)
}

observe

export function observe(value: any, asRootData: ?boolean): Observer | void {
    //判断是否为对象
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
      //创建观察者对象
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

Observer

constructor(value: any) {
    //如果是数组会有去重写数组的原型,我这边主要看walk方法
    this.walk(value)
}

walk(obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
        //这里最重要的就是重写get和set方法
      defineReactive(obj, keys[i])
    }
}

defineReactive

const dep = new Dep()

Object.defineProperty(obj, key, {

    get: function reactiveGetter() {

      const value = getter ? getter.call(obj) : val  //如果有属于自己的get方法,调用自己的get方法

      if (Dep.target) { //Dep.target就是每一个vue文件或者组件对应的Watcher对象
        dep.depend() //将创建的dep添加到Watcher中
      }

      return value
    },

    set: function reactiveSetter(newVal) {
        .....
      dep.notify() //当值发生改变时,通知更新dom
    }
  })

depend->addDep->addSub

首先看depend,Dep.target就是组件的Watcher对象

Dep

depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
}

我们再看Watcher中的addDep方法

Watcher

addDep(dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)  //将dep添加到Watcher的newDeps中
      if (!this.depIds.has(id)) {
        dep.addSub(this) //同时也将Watcher添加到dep的subs中
      }
    }
  }
Dep

addSub(sub: Watcher) {
    this.subs.push(sub)
}

现在就是你中有我,我中有你了

然后来看Watcher是怎么生成的,同时值的get是什么时候触发的

mounted

我们可以在mount时,找到一个方法mountComponent

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

mountComponent

我们留下一些关键代码。可以看到new Watcher,同时将vm._update作为回调函数传给了Watcher,

vm._update(vm._render(), hydrating)可以理解为更新dom的方法,同时vm._render()在生成虚拟dom时,会触发data中value的get方法

vm._update执行完便走到了vue生命周期的mounted

  vm.$el = el

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

  new Watcher(vm, updateComponent, noop, {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true )
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }

  return vm

Watcher

接下来看watcher

Watcher

constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)

    this.cb = cb
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn //就是update方法
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

在watcher的构造函数中调用了get方法

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

export function popTarget() {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

  get() {
    //让Dep.target指向watcher
    pushTarget(this);
    let value
    const vm = this.vm
    try {
        //调用update方法更新dom元素,同时触发defineProperty中的get方法,将dep和watcher关联起来
      value = this.getter.call(vm, vm)
    } catch (e) {
    } finally {
      if (this.deep) {
        traverse(value)
      }
      //释放Dep.target
      popTarget()
      this.cleanupDeps() //更新deps
    }
    return value
  }

到上面为止数据响应中的get已经看完了,接下来看set,也就是数据改变时

set

先回到defineReactive中,找到set方法

 set: function reactiveSetter(newVal) {
      .......
      dep.notify()
    }

然后再找到notify,通知更新

  notify() {
    const subs = this.subs.slice();
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
    //subs[i]就是Watcher对象
      subs[i].update()
    }
  }

然后找到Watcher中的update方法

watcher.update -> queueWatcher -> flushSchedulerQueue -> watcher.run

按照上面的执行顺序,直接看run就行了,中间做了什么操作可以自己去研究研究

update() {
    ......
      queueWatcher(this)
}

export function queueWatcher(watcher: Watcher) {
    .....
      nextTick(flushSchedulerQueue)
}

function flushSchedulerQueue() {
    .....
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    watcher.run()
  }
}

run

run() {
    if (this.active) {
      const value = this.get() //调用vm._update重新渲染
    }
}