整个vue架构看下来,次要分为三个局部:响应式原理,编译器的实现 以及 patch算法。
  以下是对响应式原理的集体了解,看下来,大抵是利用 公布-订阅模式 + 异步更新
  当咱们初始化一个vue实例并把它绑定到相应的DOM节点时,其实曾经实现了属性响应式的设定。
  所以咱们从VUE的构造函数动手。
  关上 core/index.js

import Vue from './instance/index'import { initGlobalAPI } from './global-api/index'import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'initGlobalAPI(Vue)Object.defineProperty(Vue.prototype, '$isServer', {  get: isServerRendering})Object.defineProperty(Vue.prototype, '$ssrContext', {  get () {    /* istanbul ignore next */    return this.$vnode && this.$vnode.ssrContext  }})// expose FunctionalRenderContext for ssr runtime helper installationObject.defineProperty(Vue, 'FunctionalRenderContext', {  value: FunctionalRenderContext})Vue.version = '__VERSION__'export default Vue

发现构造函数的根,其实在core/instance/index.js

import { initMixin } from './init'import { stateMixin } from './state'import { renderMixin } from './render'import { eventsMixin } from './events'import { lifecycleMixin } from './lifecycle'import { warn } from '../util/index'function Vue (options) {  if (process.env.NODE_ENV !== 'production' &&    !(this instanceof Vue)  ) {    warn('Vue is a constructor and should be called with the `new` keyword')  }  this._init(options)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue

咱们不细化到每一个函数,一句话,core/instance/index.js 饱满 Vue.prototype ;而 initGlobalAPI(Vue) 饱满Vue构造函数,即全局API。
以下是饱满后的Vue.prototype和Vue构造函数:

// initMixin(Vue)    src/core/instance/init.js **************************************************Vue.prototype._init = function (options?: Object) {}// stateMixin(Vue)    src/core/instance/state.js **************************************************Vue.prototype.$dataVue.prototype.$propsVue.prototype.$set = setVue.prototype.$delete = delVue.prototype.$watch = function (  expOrFn: string | Function,  cb: any,  options?: Object): Function {}// eventsMixin(Vue)    src/core/instance/events.js **************************************************Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {}Vue.prototype.$once = function (event: string, fn: Function): Component {}Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {}Vue.prototype.$emit = function (event: string): Component {}// lifecycleMixin(Vue)    src/core/instance/lifecycle.js **************************************************Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}Vue.prototype.$forceUpdate = function () {}Vue.prototype.$destroy = function () {}// renderMixin(Vue)    src/core/instance/render.js **************************************************// installRenderHelpers 函数中Vue.prototype._o = markOnceVue.prototype._n = toNumberVue.prototype._s = toStringVue.prototype._l = renderListVue.prototype._t = renderSlotVue.prototype._q = looseEqualVue.prototype._i = looseIndexOfVue.prototype._m = renderStaticVue.prototype._f = resolveFilterVue.prototype._k = checkKeyCodesVue.prototype._b = bindObjectPropsVue.prototype._v = createTextVNodeVue.prototype._e = createEmptyVNodeVue.prototype._u = resolveScopedSlotsVue.prototype._g = bindObjectListenersVue.prototype.$nextTick = function (fn: Function) {}Vue.prototype._render = function (): VNode {}// core/index.js 文件中Object.defineProperty(Vue.prototype, '$isServer', {  get: isServerRendering})Object.defineProperty(Vue.prototype, '$ssrContext', {  get () {    /* istanbul ignore next */    return this.$vnode && this.$vnode.ssrContext  }})// 在 runtime/index.js 文件中Vue.prototype.__patch__ = inBrowser ? patch : noopVue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean): Component {  el = el && inBrowser ? query(el) : undefined  return mountComponent(this, el, hydrating)}// 在入口文件 entry-runtime-with-compiler.js 中重写了 Vue.prototype.$mount 办法Vue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean): Component {  // ... 函数体}
// initGlobalAPIVue.configVue.util = {  warn,  extend,  mergeOptions,  defineReactive}Vue.set = setVue.delete = delVue.nextTick = nextTickVue.options = {  components: {    KeepAlive    // Transition 和 TransitionGroup 组件在 runtime/index.js 文件中被增加    // Transition,    // TransitionGroup  },  directives: Object.create(null),  // 在 runtime/index.js 文件中,为 directives 增加了两个平台化的指令 model 和 show  // directives:{  //  model,  //  show  // },  filters: Object.create(null),  _base: Vue}// initUse ***************** global-api/use.jsVue.use = function (plugin: Function | Object) {}// initMixin ***************** global-api/mixin.jsVue.mixin = function (mixin: Object) {}// initExtend ***************** global-api/extend.jsVue.cid = 0Vue.extend = function (extendOptions: Object): Function {}// initAssetRegisters ***************** global-api/assets.jsVue.component =Vue.directive =Vue.filter = function (  id: string,  definition: Function | Object): Function | Object | void {}// expose FunctionalRenderContext for ssr runtime helper installationObject.defineProperty(Vue, 'FunctionalRenderContext', {  value: FunctionalRenderContext})Vue.version = '__VERSION__'// entry-runtime-with-compiler.jsVue.compile = compileToFunctions

以一个例子为引

<div id="app">{{test}}</div>
var vm = new Vue({    el: '#app',    data: {        test: 1    }})

咱们晓得,实例化一个vue实例的要害,在this._init(options)上,所以让咱们走进这个函数。
core/instance/init.js

Vue.prototype._init = function (options?: Object) {  const vm: Component = this  vm._uid = uid++// vue实例ID  // 开发环境下关上性能追踪-init,compile,render,patch  let startTag, endTag  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {    startTag = `vue-perf-start:${vm._uid}`    endTag = `vue-perf-end:${vm._uid}`    mark(startTag)  }  // 以下就是被追踪性能的代码  vm._isVue = true// 标记该组件是一个vue实例  // merge options  if (options && options._isComponent) {// 不存在_isComponent属性,走else分支    initInternalComponent(vm, options)  } else {    // 初始化并饱满$options属性    vm.$options = mergeOptions(// 1.规范化属性名 2.合并对象产生新对象      resolveConstructorOptions(vm.constructor),// 解析构造函数options      options || {},      vm    )  }  if (process.env.NODE_ENV !== 'production') {    initProxy(vm)// 对vue实例的渲染做一个代理过滤  } else {    vm._renderProxy = vm  }  vm._self = vm  // 预处理-绑定将来混入生命周期等事件所需得标记位(属性)  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')  /* istanbul ignore if */  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {    vm._name = formatComponentName(vm, false)    mark(endTag)    measure(`vue ${vm._name} init`, startTag, endTag)  }  if (vm.$options.el) {    vm.$mount(vm.$options.el)  }}

这个函数一共做了几件事:

1.给实例增加ID和标识为VUE实例的标记位。
2.初始化并饱满实例的$options属性。
vm.$options = mergeOptions(// 1.规范化属性名 2.合并对象产生新对象  resolveConstructorOptions(vm.constructor),// 解析构造函数options  options || {},  vm)

相当于

vm.$options = mergeOptions(  // resolveConstructorOptions(vm.constructor)  {    components: {      KeepAlive      Transition,      TransitionGroup    },    directives:{      model,      show    },    filters: Object.create(null),    _base: Vue  },  // options || {}  {    el: '#app',    data: {      test: 1    }  },  vm)

所以咱们得去理解mergeOptions函数的实现(core/util/options.js)
次要做了两件事:
(1)规范化属性。
(2)利用相应的合并策略函数合并属性。

export function mergeOptions (  parent: Object,// 构造函数的options  child: Object,// 初始化vue实例时传入options  vm?: Component// vue实例): Object {  if (process.env.NODE_ENV !== 'production') {    checkComponents(child)// 测验options.components组件名称合法性  }  // 解决VUE.extend的状况,合并子类构造函数的options  if (typeof child === 'function') {    child = child.options  }  // 规范化属性,因为开发者有多种定义形式,须要对立  normalizeProps(child, vm)// 对立成对象的模式  normalizeInject(child, vm)// 规范化为对象语法 inject和provide配合应用  normalizeDirectives(child)// directive-注册部分指令  const extendsFrom = child.extends  if (extendsFrom) {    parent = mergeOptions(parent, extendsFrom, vm)  }  if (child.mixins) {    for (let i = 0, l = child.mixins.length; i < l; i++) {      parent = mergeOptions(parent, child.mixins[i], vm)    }  }  const options = {}  let key  for (key in parent) {    mergeField(key)  }  for (key in child) {    if (!hasOwn(parent, key)) {// 判断一个属性是否是对象本身的属性(不包含原型上的)      mergeField(key)    }  }  function mergeField (key) {    const strat = strats[key] || defaultStrat// 调用属性绝对应的策略函数,不存在则调用默认策略    options[key] = strat(parent[key], child[key], vm, key)  }  return options}

须要理解援用的strats策略对象(const strats = config.optionMergeStrategies,来自于 core/config.js ),引入在以后文件core/util/options.js中,该strats策略对象是空对象,须要在该文件中缓缓饱满本人。
咱们只剖析两个策略函数,其余便不再赘述了。
(1) 默认合并策略函数

const defaultStrat = function (parentVal: any, childVal: any): any {  return childVal === undefined    ? parentVal    : childVal}

其实很简略,同一个属性,只有子选项不是 undefined 那么就是用子选项,否则应用父选项。
(2) data属性的合并策略函数

// 定义data属性的策略函数-最终把data属性定义成一个函数,执行该函数能力失去真正的数据对象strats.data = function (  parentVal: any,  childVal: any,  vm?: Component): ?Function {  if (!vm) { // 以后解决的是子组件-阐明以后解决的是VUE.extend的状况    if (childVal && typeof childVal !== 'function') {// 子组件的data属性必须存在且为函数        process.env.NODE_ENV !== 'production' && warn(        'The "data" option should be a function ' +        'that returns a per-instance value in component ' +        'definitions.',        vm      )      return parentVal    }    return mergeDataOrFn(parentVal, childVal)  }  return mergeDataOrFn(parentVal, childVal, vm)}

发现mergeDataOrFn的返回值便是data属性策略函数,所以进入该函数。

export function mergeDataOrFn (  parentVal: any,// Vue.options 的data对象  childVal: any,// 参数 options 的data对象  vm?: Component): ?Function {  if (!vm) {// 解决VUE.extend的状况    if (!childVal) {// 子类不存在data对象,间接返回父类的data对象      return parentVal    }    if (!parentVal) {// 父类不存在data对象,间接返回子类的data对象      return childVal    }    return function mergedDataFn () {      return mergeData(// 返回真正的数据对象,去除反复属性        typeof childVal === 'function' ? childVal.call(this, this) : childVal,// 调用子类的data函数        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal// 调用父类的data函数      )    }  } else {// 初始化实例走该分支    return function mergedInstanceDataFn () {// 返回一个函数,执行后就是真正的data数据对象      const instanceData = typeof childVal === 'function'        ? childVal.call(vm, vm)        : childVal      const defaultData = typeof parentVal === 'function'        ? parentVal.call(vm, vm)        : parentVal      if (instanceData) {        return mergeData(instanceData, defaultData)// 返回真正的数据对象,去除反复属性      } else {        return defaultData      }    }  }}

到此咱们发现,data属性的策略函数,执行后会将data属性定义成一个函数,只有执行该函数能力失去真正的数据对象。
已下是vm.$options的截图

3.对vue实例的渲染做一个代理过滤。
4.预处理+调用钩子函数。

在这一步,蕴含着的initState(vm)函数,是实现响应式的根。

进入core/instance/state.js

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(vm)为切入点,看看vue是如何对data属性实现响应式的。

function initData (vm: Component) {  let data = vm.$options.data// 此时的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]    // props优先级 > data优先级 > methods优先级    if (process.env.NODE_ENV !== 'production') {      if (methods && hasOwn(methods, key)) {// 防止method和data具备同名属性        warn(          `Method "${key}" has already been defined as a data property.`,          vm        )      }    }    if (props && hasOwn(props, key)) {// 防止props和data具备同名属性      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)) {// 键名不为保留字      // 在vue实例对象上增加代理拜访数据对象的同名属性      proxy(vm, `_data`, key)// vm._data.x => vm.x    }  }  // observe data  observe(data, true /* asRootData */)}

这个函数次要做了以下几件事:
(1)获取真正的data数据对象,因为vm.$options.data是一个函数。
(2)防止data中的属性与props和methods同名。
(3)对vue实例的属性增加一层根本代理,使vm.key指向vm._data.key。

export function proxy (target: Object, sourceKey: string, key: string) {  sharedPropertyDefinition.get = function proxyGetter () {    return this[sourceKey][key]  }  sharedPropertyDefinition.set = function proxySetter (val) {    this[sourceKey][key] = val  }  Object.defineProperty(target, key, sharedPropertyDefinition)}

(4)监测data对象,使之成为响应式。

进入observe函数(core/observer/index.js)

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__// 被观测过的对象都会带有__ob__属性  } else if (    shouldObserve &&    !isServerRendering() &&    (Array.isArray(value) || isPlainObject(value)) &&    Object.isExtensible(value) &&    !value._isVue// 防止vue实例被监测  ) {    ob = new Observer(value)// 为data数据对象建设一个监测对象  }  if (asRootData && ob) {    ob.vmCount++  }  return ob}

这个函数的外围,是new Observer(value),所以进入Observer的构造函数。

export class Observer {  value: any;// data数据对象  dep: Dep;// 属于该数据对象的依赖(watcher)收集筐  vmCount: number; // number of vms that has this object as root $data  constructor (value: any) {    this.value = value    this.dep = new Dep()    this.vmCount = 0    def(value, '__ob__', this)//给数据对象定义一个__ob__属性,指向以后的observer实例,且该属性不可枚举    if (Array.isArray(value)) {// 解决数组对象      const augment = hasProto        ? protoAugment        : copyAugment      augment(value, arrayMethods, arrayKeys)      this.observeArray(value)    } else {// 解决纯对象      this.walk(value)    }  }

依据数据对象data的类型,分为两个分支(解决纯对象和解决数组)。
联合以后的例子,咱们先只看解决纯对象的状况:

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

对data数据对象中的每一个属性,调用defineReactive()办法,将数据对象的数据属性转换为拜访器属性,该办法是整个响应式的外围。

// 让属性成为响应式export function defineReactive (  obj: Object,// data  key: string,// 属性名  val: any,  customSetter?: ?Function,  shallow?: boolean) {  // 一个key 对应一个 dep   //每一个数据字段都通过闭包援用着属于本人的 dep 常量  const dep = new Dep()  // 获取属性形容对象-之前设置的第一层根本代理  const property = Object.getOwnPropertyDescriptor(obj, key)  if (property && property.configurable === false) {// 判断属性是否是可配置的    return  }  // 缓存原来设置的get set函数  const getter = property && property.get  const setter = property && property.set  if ((!getter || setter) && arguments.length === 2) {// 如果val自身领有get函数但没有set,就不会执行深度监测    val = obj[key]  }  let childOb = !shallow && observe(val)// 默认深度察看-递归  Object.defineProperty(obj, key, {    enumerable: true,    configurable: true,    get: function reactiveGetter () {      const value = getter ? getter.call(obj) : val// 调用缓存的根本get函数 vm.x=>vm._data.x      if (Dep.target) {// 要被收集的依赖-观察者watcher        dep.depend()// 将依赖收集到闭包dep的筐中        if (childOb) {//val.__ob__          childOb.dep.depend()//在应用 $set 或 Vue.set 给数据对象增加新属性时触发          if (Array.isArray(value)) {            dependArray(value)          }        }      }      return value    },    set: function reactiveSetter (newVal) {      const value = getter ? getter.call(obj) : val      /* eslint-disable no-self-compare */      if (newVal === value || (newVal !== newVal && value !== value)) {//NaN        return      }      /* eslint-enable no-self-compare */      if (process.env.NODE_ENV !== 'production' && customSetter) {        customSetter()      }      if (setter) {        setter.call(obj, newVal)// vm._data.x => vm.x      } else {        val = newVal      }      childOb = !shallow && observe(newVal)//监测新值      dep.notify()// 触发dep筐中依赖    }  })}

次要做了两件事
(1)申明一个属于属性本人的dep收集依赖筐(闭包)。
(2)改良属性的get set代理。
get代理:返回值+收集依赖

get: function reactiveGetter () {  const value = getter ? getter.call(obj) : val// 调用缓存的根本get函数 vm.x=>vm._data.x  if (Dep.target) {// 要被收集的依赖-观察者watcher    dep.depend()// 将依赖收集到闭包dep的筐中    if (childOb) {// 指向val.__ob__,解决深度监测的问题      childOb.dep.depend()// 在应用 $set 或 Vue.set 给数据对象增加新属性时触发      if (Array.isArray(value)) {// 解决属性为数组的状况        dependArray(value)      }    }  }  return value}

当获取test属性时,先判断以后存不存在要被收集的依赖(watcher对象),如果有,调用本人对应dep的depend()办法来收集依赖。(上面两个if分支在此例中不波及)
进入core/obsever/dep.js

export default class Dep {  static target: ?Watcher;// 动态对象,全局只有一个  id: number;// dep惟一标识  subs: Array<Watcher>;// 监测此dep实例的观察者们  constructor () {    this.id = uid++    this.subs = []  }  addSub (sub: Watcher) {    this.subs.push(sub)  }  removeSub (sub: Watcher) {    remove(this.subs, sub)  }  depend () {// 收集依赖(watcher)     if (Dep.target) {      Dep.target.addDep(this)    }  }  notify () {// 触发依赖    // stabilize the subscriber list first    const subs = this.subs.slice()    for (let i = 0, l = subs.length; i < l; i++) {      subs[i].update()// 调用每一个观察者的update办法    }  }}

dep.depend()就是把以后的存储在全局的Dep.target对象(watcher)增加到dep的观察者数组中。
再看看watcher对象的addDep办法,进入core/obsever/watcher.js

addDep (dep: Dep) {  const id = dep.id  if (!this.newDepIds.has(id)) {// 防止反复收集dep    this.newDepIds.add(id)    this.newDeps.push(dep)// 将以后dep对象增加到watcher实例本人的newDeps数组中    if (!this.depIds.has(id)) {      dep.addSub(this)// 将以后watcher实例增加到dep实例的观察者数组中    }  }}

临时不必去思考if分支的作用,无非是做一些性能的优化,该办法最终的成果,就是让以后的dep实例和以后的watcher实例都彼此蕴含。换句话说,dep实例在收集依赖(观察者)的同时,依赖也保留了dep实例。
set代理:设置新值+触发依赖

set: function reactiveSetter (newVal) {  const value = getter ? getter.call(obj) : val// 获取原有值  /* eslint-disable no-self-compare */  // 只有当原有值与新值不等时才触发set代理  if (newVal === value || (newVal !== newVal && value !== value)) {// NaN    return  }  /* eslint-enable no-self-compare */  if (process.env.NODE_ENV !== 'production' && customSetter) {    customSetter()  }  if (setter) {    setter.call(obj, newVal)// 调用缓存的根本set函数 vm.x => vm._data.x   } else {    val = newVal  }  childOb = !shallow && observe(newVal)//监测新值  dep.notify()// 触发dep筐中依赖}

但批改test属性时,会先判断是否等于旧值,若不等,则设置新值且调用本人对应dep的notify()办法来触发依赖。
进入core/obsever/dep.js

notify () {// 触发依赖  // stabilize the subscriber list first  const subs = this.subs.slice()  for (let i = 0, l = subs.length; i < l; i++) {    subs[i].update()// 调用每一个观察者的update办法  }}

发现无非是调用该dep实例存储的每一个依赖(watcher)的update办法。

update () {  /* istanbul ignore else */  if (this.computed) {// 解决计算属性    // A computed property watcher has two modes: lazy and activated.    // It initializes as lazy by default, and only becomes activated when    // it is depended on by at least one subscriber, which is typically    // another computed property or a component's render function.    if (this.dep.subs.length === 0) {      // In lazy mode, we don't want to perform computations until necessary,      // so we simply mark the watcher as dirty. The actual computation is      // performed just-in-time in this.evaluate() when the computed property      // is accessed.      this.dirty = true    } else {      // In activated mode, we want to proactively perform the computation      // but only notify our subscribers when the value has indeed changed.      this.getAndInvoke(() => {        this.dep.notify()      })    }  } else if (this.sync) {// 同步更新    this.run()  } else {    queueWatcher(this)// 将以后观察者对象放到一个异步更新队列  }}

在此例中,会将以后依赖(watcher)放入一个异步更新队列中。但这块并不是咱们响应式流程的重点,无非是对触发依赖的性能优化,通过上一个if分支咱们晓得,最终所有的依赖(watcher)都会执行本人的run办法。

run () {  if (this.active) {// 以后依赖(watcher)为激活状态    this.getAndInvoke(this.cb)  }}

进入getAndInvoke函数,这也是咱们依赖触发的止境

getAndInvoke (cb: Function) {  const value = this.get() // 从新收集依赖  if (    value !== this.value ||    // Deep watchers and watchers on Object/Arrays should fire even    // when the value is the same, because the value may    // have mutated.    isObject(value) ||    this.deep// 深度检测标记位,默认true  ) {    // set new value    const oldValue = this.value    this.value = value    this.dirty = false    if (this.user) {      try {        cb.call(this.vm, value, oldValue)// 调用watcher构造函数中设置的回调      } catch (e) {        handleError(e, this.vm, `callback for watcher "${this.expression}"`)      }    } else {      cb.call(this.vm, value, oldValue)    }  }}

进入watcher.get()办法

get () {  pushTarget(this)// 将此watcher实例设置为Dep.Target  let value  const vm = this.vm  try {    value = this.getter.call(vm, vm)// 获取组件中察看的属性值-获取时同时会触发属性的get  } 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()// 还原Dep.Target    this.cleanupDeps()// 清空newDeps  }  return value}

发现无非是把以后watcher设置为Dep.Target,再从新收集一次依赖。
所以getAndInvoke函数,无非做了两件事:从新收集依赖(其实还蕴含了视图更新)以及触发相应回调函数。
当初,咱们理解了响应式原理的实现机制,但咱们发现,在initData()阶段,只是对每个属性配置了相应的代理,那么在哪调用get,set? 初始的Dep.Target又是谁? 响应式批改数据后又是怎么自动更新视图的?

答案在_init()办法的最初一句代码:
if (vm.$options.el) {  vm.$mount(vm.$options.el)}

接下来,咱们将解析vue.prototype.$mount()办法,即整个响应式的终点

进入platforms/web/runtime/index.js

Vue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean): Component {  el = el && inBrowser ? query(el) : undefined// 获取实在DOM节点  return mountComponent(this, el, hydrating)// 真正的挂载工作}

这是vue.prototype.$mount()办法的第一层封装,咱们还失去src/platforms/web/entry-runtime-with-compiler.js,在这一层封装,咱们退出了编译模板的性能。

Vue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean): Component {  el = el && query(el)  /* istanbul ignore if */  if (el === document.body || el === document.documentElement) {    process.env.NODE_ENV !== 'production' && warn(      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`    )    return this  }  const options = this.$options  // resolve template/el and convert to render function  if (!options.render) {    let template = options.template    if (template) {      if (typeof template === 'string') {        if (template.charAt(0) === '#') {          template = idToTemplate(template)          /* istanbul ignore if */          if (process.env.NODE_ENV !== 'production' && !template) {            warn(              `Template element not found or is empty: ${options.template}`,              this            )          }        }      } else if (template.nodeType) {        template = template.innerHTML      } else {        if (process.env.NODE_ENV !== 'production') {          warn('invalid template option:' + template, this)        }        return this      }    } else if (el) {      template = getOuterHTML(el)    }    if (template) {      /* istanbul ignore if */      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {        mark('compile')      }      const { render, staticRenderFns } = compileToFunctions(template, {        shouldDecodeNewlines,        shouldDecodeNewlinesForHref,        delimiters: options.delimiters,        comments: options.comments      }, this)      options.render = render      options.staticRenderFns = staticRenderFns      /* istanbul ignore if */      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {        mark('compile end')        measure(`vue ${this._name} compile`, 'compile', 'compile end')      }    }  }  return mount.call(this, el, hydrating)}

看下来,在这层封装中,无非是对没有render函数的vue实例,通过template编译出render函数。

最终还是要调用 mountComponent(this, el, hydrating)。即这个函数才是真正的挂载函数,之前的操作无非是为了给它提供渲染函数。

进入core/instance/lifecycle.js

export function mountComponent (  vm: Component,  el: ?Element,  hydrating?: boolean): Component {  vm.$el = el  if (!vm.$options.render) {    vm.$options.render = createEmptyVNode    if (process.env.NODE_ENV !== 'production') {      /* istanbul ignore if */      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||        vm.$options.el || el) {        warn(          'You are using the runtime-only build of Vue where the template ' +          'compiler is not available. Either pre-compile the templates into ' +          'render functions, or use the compiler-included build.',          vm        )      } else {        warn(          'Failed to mount component: template or render function not defined.',          vm        )      }    }  }  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}

在该函数中,总共做了几件事:

1.判断渲染函数是否存在,不存在定义为一个空VNode节点。

2.定义并初始化updateComponent函数。

3.为updateComponent函数申明一个观察者对象。

咱们先看updateComponent函数,发现无论是执行 if 语句块还是执行 else 语句块,最终 updateComponent 函数的性能是不变的。都是以 vm._render() 函数的返回值作为第一个参数调用 vm._update() 函数,即把渲染函数生成的虚构DOM渲染成真正的DOM

不深究那两个子函数,能够简略地认为:

vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚构节点(vnode)。

vm._update 函数的作用是把 vm._render 函数生成的虚构节点渲染成真正的 DOM。

接着,咱们看创立观察者的局部,这也是真正触发响应式的要害。

创立 Watcher 观察者实例将对 updateComponent 函数求值,咱们晓得 updateComponent 函数的执行会间接触发渲染函数(vm.$options.render)的执行,而渲染函数的执行则会触发数据属性的 get 拦截器函数,从而将依赖(观察者)收集,当数据变动时将从新执行 updateComponent 函数,这就实现了从新渲染。同时咱们把下面代码中实例化的观察者对象称为渲染函数的观察者。

最初,咱们进入core/observer/watcher.js做最初的摸索。

export default class Watcher {  vm: Component;  expression: string;  cb: Function;  id: number;  deep: boolean;  user: boolean;  computed: boolean;  sync: boolean;  dirty: boolean;  active: boolean;  dep: Dep;  deps: Array<Dep>;  newDeps: Array<Dep>;  depIds: SimpleSet;  newDepIds: SimpleSet;  before: ?Function;  getter: Function;  value: any;  constructor (    vm: Component,    expOrFn: string | Function,    cb: Function,    options?: ?Object,    isRenderWatcher?: boolean  ) {    this.vm = vm    if (isRenderWatcher) {      vm._watcher = this    }    vm._watchers.push(this)    // options    if (options) {      this.deep = !!options.deep      this.user = !!options.user      this.computed = !!options.computed      this.sync = !!options.sync      this.before = options.before    } else {      this.deep = this.user = this.computed = this.sync = false    }    this.cb = cb    this.id = ++uid // uid for batching    this.active = true    this.dirty = this.computed // for computed watchers    this.deps = []    this.newDeps = []    this.depIds = new Set()    this.newDepIds = new Set()    this.expression = process.env.NODE_ENV !== 'production'      ? expOrFn.toString()      : ''    // parse expression for getter    if (typeof expOrFn === 'function') {      this.getter = expOrFn    } else {      this.getter = parsePath(expOrFn)      if (!this.getter) {        this.getter = function () {}        process.env.NODE_ENV !== 'production' && warn(          `Failed watching path: "${expOrFn}" ` +          'Watcher only accepts simple dot-delimited paths. ' +          'For full control, use a function instead.',          vm        )      }    }    if (this.computed) {      this.value = undefined      this.dep = new Dep()    } else {      this.value = this.get()    }  }

以后的expOfFn指向updateComponent函数,此时被赋值到this.getter。关注到最初一行的this.value=this.get();

get () {  pushTarget(this)// 将此watcher实例设置为Dep.Target  let value  const vm = this.vm  try {    value = this.getter.call(vm, vm)// 获取组件中察看的属性值-获取时同时会触发属性的get  } 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()// 还原Dep.Target    this.cleanupDeps()// 清空newDeps  }  return value}

发现以后的渲染函数观察者被设置到Dep.Target上了,且触发this.getter就是执行updateComponent函数,执行时会触发相应属性的get代理。

当初所有都捋顺了,咱们来梳理一下流程。

咱们new一个vue实例的同时,会触发_init函数,这个函数做了几件事:

(1)初始化并饱满vm.$options属性。

(2)调用相应的初始化函数,让数据成为响应式。其中要害的initState(vm),会让设置的data,methods,props对象,成为响应式。

例如其中的initData(vm),会调用observe(data),即为data数据对象建设一个监测对象observer,具体的就是为纯对象调用walk(data)(数组对象递归调用observe()),该函数外部就是为data对象中的每一个属性调用defineReactive(obj,key)函数,使该属性成为响应式。而属性成为响应式的要害,就是

 1)为每个属性设置对应的dep,由它来做公布信息的工作。

 2)设置get代理来收集依赖。

 3)设置set代理来触发依赖。

(3)将vue实例挂载到dom节点上。咱们在这一步为渲染函数设置了一个观察者watcher,这也是咱们整个响应式的终点。咱们在挂载的时候,会调用渲染函数,从而触发相干数据属性的 get 拦截器函数,从而将以后依赖(渲染函数观察者)收集,此时,每一个属性都收集着以后渲染函数观察者。将来,当数据变动时将触发依赖的update()函数->run()->getAndInvoke(),其中的this.get(),会从新执行 updateComponent 函数,这就实现了从新渲染。