家喻户晓,Vue是以数据驱动视图展现的,即Vue会监听数据的变动,从而主动从新渲染页面的构造。

Vue次要通过三步走来实现这个性能:

第一步是对数据进行响应式革新,即对数据的读写操作进行劫持;

第二步是对模板依赖的数据进行收集;

第三步是在数据发生变化时,触发组件更新。

数据响应式革新

0. defineReactive

对数据进行响应式革新的外围代码

// core/observer/index.jsexport function defineReactive (  obj: Object,  key: string,  val: any,  customSetter?: ?Function,  shallow?: boolean) {  const dep = new Dep()  const property = Object.getOwnPropertyDescriptor(obj, key)  if (property && property.configurable === false) {    return  }  // cater for pre-defined getter/setters  const getter = property && property.get  const setter = property && property.set  if ((!getter || setter) && arguments.length === 2) {    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      if (Dep.target) { // 以后`Dep.target`不为空时,通常指向一个`watcher`实例        dep.depend() // 属性被收集到以后`watcher`实例的依赖数组中        if (childOb) {          childOb.dep.depend()          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)) {        return      }      /* eslint-enable no-self-compare */      if (process.env.NODE_ENV !== 'production' && customSetter) {        customSetter()      }      // #7981: for accessor properties without setter      if (getter && !setter) return      if (setter) {        setter.call(obj, newVal)      } else {        val = newVal      }      childOb = !shallow && observe(newVal)      dep.notify()    }  })}

通过Object.defineProperty批改对象属性的属性描述符descriptor,来实现劫持对象属性的读写操作。

前置常识,对象的属性分为data型和accessor型。

data型的属性描述符蕴含value和writable;accessor型的属性描述符蕴含getter和setter函数(两者至多存在一个)。

由上述代码能够看出,所有属性被解决成了accessor型属性,即通过getter和setter来实现读写,比方当咱们读取person对象上的属性name,理论失去的是name的属性拜访符中的getter函数执行后的返回值。

上述的响应式革新中,每个属性会对应一个dep实例:const dep = new Dep(),如果属性值val是对象或数组,会被列入察看对象,他的属性会被递归进行响应式革新let childOb = !shallow && observe(val)

get函数被用于收集依赖

get: function reactiveGetter () {      const value = getter ? getter.call(obj) : val      if (Dep.target) {        dep.depend()        if (childOb) {          childOb.dep.depend()          if (Array.isArray(value)) {            dependArray(value)          }        }      }      return value    },

该函数在对象属性被拜访时会执行,如果Dep.target不为空,即当下有一个监听器watcher在收集依赖,就进行依赖的收集,dep实例会被收集到该watcher的依赖数组newDeps中,同时dep也会将此watcher记录到本人的subs订阅数组中,记录有谁订阅了本人的变更。

如果childOb不为空(即属性值val为数组或对象,且可扩大),就对val的__ob__属性也进行收集操作。

如果value是数组,对数组中的对象元素也进行依赖收集。

就是一层层的递归收集。

set函数被用于告诉变更:

set: function reactiveSetter (newVal) {      const value = getter ? getter.call(obj) : val      /* eslint-disable no-self-compare */      if (newVal === value || (newVal !== newVal && value !== value)) {        return      }      /* eslint-enable no-self-compare */      if (process.env.NODE_ENV !== 'production' && customSetter) {        customSetter()      }      // #7981: for accessor properties without setter      if (getter && !setter) return      if (setter) {        setter.call(obj, newVal)      } else {        val = newVal      }      childOb = !shallow && observe(newVal)      dep.notify()    }

如果属性的新值是属性或对象,就更新childOb

实现属性赋值操作后,调用dep.notify(),告诉所有订阅了本人的watcher实例执行update操作,即上面代码中的for循环操作。

// core/observer/dep.jsexport default class Dep {  // ...  notify () {    // stabilize the subscriber list first    const subs = this.subs.slice()    if (process.env.NODE_ENV !== 'production' && !config.async) {      // subs aren't sorted in scheduler if not running async      // we need to sort them now to make sure they fire in correct      // order      subs.sort((a, b) => a.id - b.id)    }    for (let i = 0, l = subs.length; i < l; i++) {      subs[i].update()    }  }}

如果是同步this.sync的watcher会立刻被执行,否则会插入到watcher队列queueWatcher(this)排队期待执行:

// core/observer/watcher.jsupdate () {    /* istanbul ignore else */    if (this.lazy) {      this.dirty = true    } else if (this.sync) {      this.run()    } else {      queueWatcher(this)    }  }

1. initInjections

// core/instance/inject.jsexport function initInjections (vm: Component) {  const result = resolveInject(vm.$options.inject, vm)  if (result) {    toggleObserving(false)    Object.keys(result).forEach(key => {      /* istanbul ignore else */      if (process.env.NODE_ENV !== 'production') {        defineReactive(vm, key, result[key], () => {          warn(            `Avoid mutating an injected value directly since the changes will be ` +            `overwritten whenever the provided component re-renders. ` +            `injection being mutated: "${key}"`,            vm          )        })      } else {        defineReactive(vm, key, result[key])      }    })    toggleObserving(true)  }}

通过执行resolveInject解析inject中的数据,解析后果赋值给result。result蕴含inject中所有的key,如果下级组件中没有对应inject数据的provide,就赋默认值,简略来说大抵就是result[key] = inject[key] || default

再调用defineReactive(vm, key, result[key])将这些key加到vm实例上,即inject中的数据也会进行响应式解决。

假如存在一个inject:["person"],如果person的值是个对象,它的值会被列为察看对象,以后子组件的watcher会对该对象的属性依赖收集,在下级组件中更改了原始person的某个属性,就会触发子组件的更新。

2. initProps

// core/instance/state.jsfunction initProps (vm: Component, propsOptions: Object) {  const propsData = vm.$options.propsData || {}  const props = vm._props = {}  // cache prop keys so that future props updates can iterate using Array  // instead of dynamic object key enumeration.  const keys = vm.$options._propKeys = []  const isRoot = !vm.$parent  // root instance props should be converted  if (!isRoot) {    toggleObserving(false)  }  for (const key in propsOptions) {    keys.push(key)    const value = validateProp(key, propsOptions, propsData, vm)    /* istanbul ignore else */    if (process.env.NODE_ENV !== 'production') {      const hyphenatedKey = hyphenate(key)      if (isReservedAttribute(hyphenatedKey) ||          config.isReservedAttr(hyphenatedKey)) {        warn(          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,          vm        )      }      defineReactive(props, key, value, () => {        if (!isRoot && !isUpdatingChildComponent) {          warn(            `Avoid mutating a prop directly since the value will be ` +            `overwritten whenever the parent component re-renders. ` +            `Instead, use a data or computed property based on the prop's ` +            `value. Prop being mutated: "${key}"`,            vm          )        }      })    } else {      defineReactive(props, key, value)    }    // static props are already proxied on the component's prototype    // during Vue.extend(). We only need to proxy props defined at    // instantiation here.    if (!(key in vm)) {      proxy(vm, `_props`, key)    }  }  toggleObserving(true)}

propsOptions,接管的是vm.$options.props,是申明接管的props的配置,;vm.$options.propsData是理论接管到的props数据。

调用defineReactive(props, key, result[key])将propsOptions上的key加到props对象上,即vm._props上,进行响应式解决,如果是在vm上不存在的key,通过proxy(vm, '_props', key)操作,使得能够通过vm间接拜访到_props的属性,而不须要通过_props对象来拜访。

3. initData

// core/instance/state.jsfunction 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 */)}

data选项会被挂在vm._data上,从上述代码中能够看出,data必须是一个对象,或者返回值为对象的函数。

通过proxy(vm, '_data', key)操作,vm能够间接拜访到_data的属性,而不须要通过_data对象来拜访。

最初通过observe(data, true /* asRootData */)来对数据做响应式革新,能够看到这个observe办法多传了一个参数值为true,标记以后解决的数据是$options.data对象。

observe办法理论是创立一个新的ob实例,数据的__ob__属性置信在控制台打印过vue中数据的同学都不生疏,都是指向ob实例。

// core/observe/index.jsexport 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}
// core/observe/index.jsexport class Observer {  value: any;  dep: Dep;  vmCount: number; // number of vms that have this object as root $data  constructor (value: any) {    this.value = value    this.dep = new Dep()    this.vmCount = 0    def(value, '__ob__', this)    if (Array.isArray(value)) {      if (hasProto) {        protoAugment(value, arrayMethods)      } else {        copyAugment(value, arrayMethods, arrayKeys)      }      this.observeArray(value)    } else {      this.walk(value)    }  }  /**   * Walk through all properties and convert them into   * getter/setters. This method should only be called when   * value type is Object.   */  walk (obj: Object) {    const keys = Object.keys(obj)    for (let i = 0; i < keys.length; i++) {      defineReactive(obj, keys[i])    }  }  /**   * Observe a list of Array items.   */  observeArray (items: Array<any>) {    for (let i = 0, l = items.length; i < l; i++) {      observe(items[i])    }  }}

从下面Observer的构造函数中能够看出,创立ob实例后,这个实例就挂载数据的__ob__属性上了,因为在iniDats时传递给构造函数的参数是个对象,所以会调用walk办法,持续看walk办法的定义,能够看出,是把这个对象的属性一一取出,调用defineReactive(obj, keys[i])进行响应式革新。

4. initComputed

// core/instance/state.jsfunction initComputed (vm: Component, computed: Object) {  // $flow-disable-line  const watchers = vm._computedWatchers = Object.create(null)  // computed properties are just getters during SSR  const isSSR = isServerRendering()  for (const key in computed) {    const userDef = computed[key]    const getter = typeof userDef === 'function' ? userDef : userDef.get    if (process.env.NODE_ENV !== 'production' && getter == null) {      warn(        `Getter is missing for computed property "${key}".`,        vm      )    }    if (!isSSR) {      // create internal watcher for the computed property.      watchers[key] = new Watcher(        vm,        getter || noop,        noop,        computedWatcherOptions      )    }    // component-defined computed properties are already defined on the    // component prototype. We only need to define computed properties defined    // at instantiation here.    if (!(key in vm)) {      defineComputed(vm, key, userDef)    } else if (process.env.NODE_ENV !== 'production') {      if (key in vm.$data) {        warn(`The computed property "${key}" is already defined in data.`, vm)      } else if (vm.$options.props && key in vm.$options.props) {        warn(`The computed property "${key}" is already defined as a prop.`, vm)      } else if (vm.$options.methods && key in vm.$options.methods) {        warn(`The computed property "${key}" is already defined as a method.`, vm)      }    }  }}

取得每个计算属性对应watcher的初始值

从上述代码中能够看出,会遍历computed所有的属性,每个属性对应配置一个watcher实例,watcher实例在创立时,会调用每个computed对应的getter获取一遍初始值,放在watcher实例的value属性上

// core/observer/watcher.jsexport default class 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.value = this.lazy      ? undefined      : this.get()  }  /**   * Evaluate the getter, and re-collect dependencies.   */  get () {    pushTarget(this)    let value    const vm = this.vm    try {      value = this.getter.call(vm, vm)    } 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()      this.cleanupDeps()    }    return value  }  // ...}
// core/observer/dep.jsexport function pushTarget (target: ?Watcher) {  targetStack.push(target)  Dep.target = target}

能够看到,执行watcher实例的get()办法时,会进行一个pushTarget(this)的操作,此操作批改了Dep.target,使它指向了以后的watcher实例,如果某个computed属性依赖了data中的某个属性,须要读取data中的某个属性值,就会触发该data属性的getter函数,使得该data属性被收集到以后watcher实例的依赖数组中。

实现computed属性的取值后,执行popTarget(),即上面的代码:

// core/observer/dep.jsexport function popTarget () {  targetStack.pop()  Dep.target = targetStack[targetStack.length - 1]}

会使Dep.target指回上一个watcher实例。

最初清理依赖this.cleanupDeps(),将不再关联的依赖dep其订阅数组中对应的watcher移除,将newDeps赋值给deps并清空newDeps,代表该watcher实例一次依赖收集结束。

计算属性被读取时,其对应watcher依赖的数据会被以后watcher收集为本身的依赖

如果computed某个属性的标识符不在vm实例上,就继续执行defineComputed(vm, key, userDef),会将给vm实例增加一个名为key的属性,该属性的getter函数由下述代码定义:

// core/instance/state.jsfunction createComputedGetter (key) {  return function computedGetter () {    const watcher = this._computedWatchers && this._computedWatchers[key]    if (watcher) {      if (watcher.dirty) {        watcher.evaluate()      }      if (Dep.target) {        watcher.depend()      }      return watcher.value    }  }}

即在这个计算属性被读取时,会拿到它所对应的watcher实例,如果以后Dep.target不为null时,watcher会执行实例办法depend()

export default class Watcher {  // ...  /**   * Depend on all deps collected by this watcher.   */  depend () {    let i = this.deps.length    while (i--) {      this.deps[i].depend()    }  }  // ...}

能够看到此watcher实例的依赖deps会被一一取出,执行dep实例的depend办法:

// core/observerdep.jsdepend () {    if (Dep.target) {      Dep.target.addDep(this)    }  }

即此watcher实例的依赖都会被收集到以后Dep.target指向的watcher实例的依赖数组中。

5. initWatch

// core/instance/state.jsfunction initWatch (vm: Component, watch: Object) {  for (const key in watch) {    const handler = watch[key]    if (Array.isArray(handler)) {      for (let i = 0; i < handler.length; i++) {        createWatcher(vm, key, handler[i])      }    } else {      createWatcher(vm, key, handler)    }  }}function createWatcher (  vm: Component,  expOrFn: string | Function,  handler: any,  options?: Object) {  if (isPlainObject(handler)) {    options = handler    handler = handler.handler  }  if (typeof handler === 'string') {    handler = vm[handler]  }  return vm.$watch(expOrFn, handler, options)}Vue.prototype.$watch = function (    expOrFn: string | Function,    cb: any,    options?: Object  ): Function {    const vm: Component = this    if (isPlainObject(cb)) {      return createWatcher(vm, expOrFn, cb, options)    }    options = options || {}    options.user = true    const watcher = new Watcher(vm, expOrFn, cb, options)    if (options.immediate) {      const info = `callback for immediate watcher "${watcher.expression}"`      pushTarget()      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)      popTarget()    }    return function unwatchFn () {      watcher.teardown()    }  }

initWatch的内容比较简单,就是通过调用createWatcher(vm, key, handler),一一对应生成watcher实例,并且给watcher实例标记options.user = true,代表这个watcher是用户配置的。

每个watch通常是有一个对应的表达式(通常是vm的data数据)和一个对应的回调函数,应用场景通常是当vm中的某些数据产生扭转时,用户须要做一些自定义的操作来做解决。

与computed中生成对应watcher实例相似,watcher实例在创立时,每个watch对应的表达式就会被求值一遍,即vm实例上的某些数据属性被读取,这些属性对应的dep会被收集到该watcher实例的依赖数组中,求得的值会放在watcher实例的value属性上,如果某个watch配置了immediate,就立刻执行一遍watch对应的回调函数,入参为watchervalue属性值。

能够看到在执行回调函数前,执行了一个pushTarget(),此时Dep.target会指向空,所以在回调函数执行过程中,如果vm的某些数据属性被拜访,这些属性不会被收集依赖,因为属性的getter函数中在属性被收集依赖前有个对Dep.target的判空查看。

6. 一些阐明

__ob__是给对象加的属性,指向observer实例,ob和对象是一对一,代表这个对象被察看,该对象的属性的读写操作会被做响应式解决,即被劫持。

dep是给属性配置的用于依赖收集的,通常对象的某个属性与dep是一对一,能够被多个watcher收集,即多个watcher实例在监听这个属性的变动;__ob__是对象的非凡属性,它也有本人的dep,能够被watcher收集。

一个watcher实例只会关联一个vm,一个vm实例能够关联多个watcher,watcher实例会放在vm._watchers数组中;渲染watcher还会放在vm._watcher上,渲染watcher从字面上了解就是与组件渲染无关的watcher