关于前端:浅读vue源码2x之响应式原理

30次阅读

共计 24383 个字符,预计需要花费 61 分钟才能阅读完成。

  整个 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 installation
Object.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.$data
Vue.prototype.$props
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.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 = markOnce
Vue.prototype._n = toNumber
Vue.prototype._s = toString
Vue.prototype._l = renderList
Vue.prototype._t = renderSlot
Vue.prototype._q = looseEqual
Vue.prototype._i = looseIndexOf
Vue.prototype._m = renderStatic
Vue.prototype._f = resolveFilter
Vue.prototype._k = checkKeyCodes
Vue.prototype._b = bindObjectProps
Vue.prototype._v = createTextVNode
Vue.prototype._e = createEmptyVNode
Vue.prototype._u = resolveScopedSlots
Vue.prototype._g = bindObjectListeners
Vue.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 : noop
Vue.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 {// ... 函数体}
// initGlobalAPI
Vue.config
Vue.util = {
  warn,
  extend,
  mergeOptions,
  defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
Vue.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.js
Vue.use = function (plugin: Function | Object) {}
// initMixin ***************** global-api/mixin.js
Vue.mixin = function (mixin: Object) {}
// initExtend ***************** global-api/extend.js
Vue.cid = 0
Vue.extend = function (extendOptions: Object): Function {}
// initAssetRegisters ***************** global-api/assets.js
Vue.component =
Vue.directive =
Vue.filter = function (
  id: string,
  definition: Function | Object
): Function | Object | void {}
// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {value: FunctionalRenderContext})
Vue.version = '__VERSION__'
// entry-runtime-with-compiler.js
Vue.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 函数,这就实现了从新渲染。

正文完
 0