深入学习vue系列 —— 响应式原理及双向数据绑定原理

2,434 阅读9分钟

首先来一个问题

数据响应式原理真的是双向数据绑定吗?

  • 数据响应式原理:

    通过数据的改变去驱动 DOM 视图的变化。

  • 双向数据绑定:

    双向绑定除了数据驱动 DOM 之外, DOM 的变化反过来影响数据,是一个双向的关系。

总结:

所以说,把 vue 的数据响应原理理解为双向数据绑定,实际上这是不准确的。

双向数据绑定

在 Vue 中体现出双向数据绑定作用的方式有两种:

1)v-model 属性

针对于 input 的 v-model 双向数据绑定实际上就是通过子组件中的 $emit 方法派发 input 事件,父组件监听 input 事件中传递的 value 值,并存储在父组件 data 中;然后父组件再通过 prop 的形式传递给子组件 value 值,再子组件中绑定 input 的 value 属性即可。

其他元素使用 v-model 双向数据绑定实际上就是,通过监听 change 事件。以及$emit 方法派发,再通过 prop 的形式传递。

2).sync 修饰符

父组件向子组件传递数据的方式有多种,props 是其中的一种,但是它的局限在于数据只能单向传递,子组件不能直接修改 prop 属性,但是碰到子组件需要修改父组件的情况怎么办呢?

父组件中并没有定义过 update 事件,但是却可以完成 prop 属性 page 的修改,这就是 sync 语法糖的作用。

响应式原理

Vue 采用的是数据劫持结合发布和-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

initState 函数是很多选项初始化的汇总,在 initState 函数内部使用 initProps 函数初始化props 属性;使用 initMethods 函数初始化 methods 属性;使用 initData 函数初始化 data选项;使用 initComputed 函数和 initWatch 函数初始化 computed 和 watch 选项。 initData 为切入点来了解一下 Vue 的响应系统。

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)
  }
}

下面代码对 data 做个判断,如果存在则调用 initData(vm) 函数初始化 data 选项,否则通过 observe 函数观测一个空的对象。其中 observe 函数是将 data 转换成响应式数据的核心入口。

if (opts.data) {
    initData(vm)
} else {
    observe(vm._data = {}, true /* asRootData */)
}

下面我们对 initData 函数解析

function initData (vm: Component) {
  // data 的赋值操作
  let data = vm.$options.data
  // typeof 判断的原始因为beforeCreate 生命周期钩子函数是在 
  // 选项合并阶段之后 initData 之前被调用的,
  // 如果在 beforeCreate 生命周期钩子函数中修改了 vm.$options.data 的值,
  // 那么在 initData 函数中对于 vm.$options.data 类型的判断就是有存在的必要了。
  
  // vm.$options.data 的类型为函数,则调用 getData 函数获取真正的数据
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
    
  // isPlainObject 函数判断变量 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') {
      // 判断data 数据的 key 与 methods 对象中定义的函数名称相同时,发出警告
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 判断data 数据的 key 与 props 中定义的函数名称相同时,发出警告
    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
      )
    // 判断定义在 data 中的 key 是否是保留键
    // isReserved 函数通过判断一个字符串的
    // 第一个字符是不是 $ 或 _ 来决定其是否是保留的
    // 这么做是为了避免与 Vue 自身的属性和方法相冲突
    // Vue 是不会代理那些键名以 $ 或 _ 开头的字段的
    // 因为Vue自身的属性和方法都是以 $ 或 _ 开头的
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

对于以上代码主要对 getData 和 proxy 函数做解析

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

上述代码解析

getData 函数的作用其实就是通过调用 data 函数获取真正的数据对象并返回,即:data.call(vm, vm),如果有错误发生那么则返回一个空对象作为数据对象。

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)
}

上述代码解析

proxy 函数的原理是通过 Object.defineProperty 函数在实例对象 vm 上定义与 data 数据字段同名的访问器属性,并且这些属性代理的值是 vm._data 上对应属性的值。

举例说明:

const vm = new Vue ({
  data: {
    count: 1
  }
})

因为有 proxy 函数的原因,当我们访问 vm.count时,实际上访问的是vm._data.count。就是起到了一个数据代理的意思。

下面进入我们的核心正式进入响应式之路。

observe(data, true /* asRootData */)

observe 函数观测数据

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 数据不是一个对象或者是 VNode 实例,则直接 return 
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 判断value 是否有"__ob__" 属性
  // 如果有是否为 Observer 实例,两者满足把 value.__ob__ 值赋值给ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&  // 用来判断是否是服务端渲染
    // 被观测的数据对象必须是数组或者纯对象
    (Array.isArray(value) || isPlainObject(value)) &&
    // 观测的数据对象必须是可扩展的
    Object.isExtensible(value) &&
    // Vue实例才拥有_isVue 属性,在此是避免观测Vue实例对象
    !value._isVue
  ) {
    // 创建一个Observer实例
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

observe 函数接收两个参数,第一个参数是要观测的数据,第二个参数是一个布尔值,代表将要被观测的数据是否是根级数据。

真正将数据对象转换成响应式数据对象的是 Observer 函数

Observer 构造函数:

首先实例化 Dep 对象,接着通过执行 def 函数把自身实例添加到数据对象 value 的 __ob__ 属性上。然后是对数据的分类处理,最终分别调用 defineReactive 函数,定义响应式对象。

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

export 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 函数,为数据对象定义了一个 __ob__ 属性,
    // 这个属性的值就是当前 Observer 实例对象。
    def(value, '__ob__', this)
    // 判断是否为纯数组,不是数组执行walk函数
    if (Array.isArray(value)) {
      // hasProto 是一个布尔值,它用来检测当前环境是否可以使用__proto__属性
      // 浏览器支持隐式的原型__proro__,则调用protoAugment方法
      // arrayMethods 对数组方法的重写
      if (hasProto) {
        // 直接将数组的实例通过__proto__与arrayMethods对象连接起来。
        // 从而继承了arrayMethods上的方法
        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])
    }
  }
}

arrayMethods 对数组方法的重写。

由于 Object.defineProperty 对数组的劫持与对象一样,会把数组的索引当作key来监听数组,但是只能监听初始的索引变化。如果使用push或shift来增加索引,默认情况是监听不到的,所以需要再手动初始化才能被observer 。

// 缓存数组原型
const arrayProto = Array.prototype
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto)
// 需要进行功能拓展的方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  // 缓存原生数组方法
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
  	// 执行并缓存原生数组功能
    const result = original.apply(this, args)
    // 响应式处理
    const ob = this.__ob__
    let inserted
    switch (method) {
      // push、unshift会新增索引,所以要手动observer
      case 'push':
      case 'unshift':
        inserted = args
        break
      // splice方法,如果传入了第三个参数,也会有索引加入,也要手动observer。
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 获取插入的值,并设置响应式监听
    if (inserted) ob.observeArray(inserted)
    // notify change
    // 通知依赖更新
    ob.dep.notify()
    // 返回原生数组方法的执行结果
    return result
  })
})

关于 def 函数

def 函数是一个非常简单的Object.defineProperty 的封装,这就是为什么我在开发中输出 data 上对象类型的数据,会发现该对象多了一个 __ob__ 的属性。

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

好了,说了这么多,重点要来了

defineReactive 函数

defineReactive 的功能就是定义一个响应式对象,给对象动态添加 getter 和 setter。并且实现对数据的依赖收集以及派发更新

export 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
  // 获取原来的get、set
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 递归:继续监听该属性值(只有val为对象时才有childOb)
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 计算value
      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
    },
    set: function reactiveSetter (newVal) {
      // 计算value
      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
      }
      // 劫持新值到 observe 函数中处理
      childOb = !shallow && observe(newVal)
      // 发送变更通知
      dep.notify()
    }
  })
}

// dependArray  函数查看每项是否含观察者对象,有则添加依赖
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}

Object.defineProperty 方法内的 getter 实现了依赖的收集。 setter 实现了派发更新,而依赖的收集与派发更新都与 Dep 和 Watcher 息息相关,下面我们对这两个函数源码进行分析。

订阅中心 Dep

export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
  	//每个Dep都有唯一的ID
    this.id = uid++
    // 订阅的信息 用于存放依赖
    this.subs = []
  }
  // 向 subs 数组添加依赖	
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  //移除依赖
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
  // 可以理解为收集依赖的事件,不考虑其他方面的话 功能等同于addSub()
  // 此方法的作用等同于 this.subs.push(Watcher);
  // 也及是调用watcher的adddep方法实现watcher和dep相互引用
  // 设置某个Watcher的依赖
  // 这里添加了Dep.target是否存在的判断,目的是判断是不是Watcher的构造函数调用
  // 也就是说判断他是Watcher的this.get调用的,而不是普通调用
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  // 这个方法更为直观了,执行所有依赖的update()方法,改变视图。
  // 通知所有绑定 Watcher。调用watcher的update()
  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()
    }
  }
}
Dep.target = null;

订阅者 Watcher

在initMixin()初始化完成Vue实例所有的配置之后,在最后根据el是否存在,调用$mount()实现挂载。

if (vm.$options.el) {
	vm.$mount(vm.$options.el);
}

在 $mount 中最后 执行的是return mountComponent(this, el, hydrating)

在 mountComponent() 方法中记住以下两段代码

updateComponent = function () {
	vm._update(vm._render(), hydrating);
};
new Watcher(vm, updateComponent, noop, {
	before: function before () {
		if (vm._isMounted) {
			callHook(vm, 'beforeUpdate');
		}
	}
}, true /* isRenderWatcher */);

mountComponent 在构造新的Watcher对象传了当前vue实例、updateComponent函数、空函数、options 还有个布尔值。

下面看一下watcher 的源码:

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    // 当前Watcher添加到vue实例上
    vm._watchers.push(this)
    // 参数配置,options默认false
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
    // this.lazy 如果设置为 true 则在第一次 get 的时候才计算值
    // 初始化的时候并不计算。默认值为 true
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    // 用于计算属性
    this.dirty = this.lazy // for lazy 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
    //将watcher对象的getter设为updateComponent方法
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        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
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

get 函数会把我们 watcher 推送到,dep.target 中

Watcher 的 get 方法实际上就是调用了 updateComponent 方法,调用这个函数会接着调用 _update 函数更新 dom ,这个是挂载到 vue 原型的方法,而_render方法重新渲染了 vnode 。


get () {
  // 将Dep的target添加到targetStack,同时Dep的target赋值为当前watcher对象
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    // 调用updateComponent方法。getter 属于取值操作。
    // 就会调用defineReactive 中的 getter 函数。
    // 所以此时会执行 dep.depend() 函数 调用 watcher.addDep(dep)
    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)
    }
    // update执行完成后,又将Dep.target从targetStack弹出。
    // pushTarget(this) 方法与 popTarget()方法主要做的就是入栈出栈的一个操作
    popTarget()
    this.cleanupDeps()
  }
  return value
}

addDep 函数

addDep (dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
      // 过滤去重后,对依赖进行收集
      dep.addSub(this)
    }
  }
}

当执行 addDep 的时候会把 dep 存起来,这个函数明显是用来去重的。

当调用 this.cleanupDeps ,这个函数会把 newDepIds 的值赋给 depIds,然后把 newDepIds 清空。

update 函数

当执行到 dep.notify 时,也就是调用 watcher.update() 方法。watcher.update 函数默认是异步更新的。所以默认会走 queueWatcher 方法

update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
}

queueWatcher 函数

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  
  /* 
  * 注意这里是==而不是===
  * 如果has[id]不存在,则has[id]为undefined,undefined==null结果为true
  * 如果has[id]存在且为null,则为true
  * 如果has[id]存在且为true,则为false
  * 这个if表示,如果这个watcher尚未被flush则return
  */
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
       /* 如果当前不是正在更新watcher数组的话,那watcher会被直接添加到队列末尾 */
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      /* 这个循环其实是在处理边界情况。
      * 即:在watcher队列更新过程中,用户再次更新了队列中的某个watcher      
      */
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        // 调用 flushSchedulerQueue() 刷新队列并执行 watcher。
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

flushSchedulerQueue 函数

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  
  // 以watcher.id对watcher进行有小到大排序 */
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  /* 这里没有将queue.length缓存起来是因为在执行期间还会添加进新的watcher */
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      /* 一般是执行beforeUpdate钩子函数 */
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    /* run方法会执行watcher的callback方法 */
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }

  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}

上述函数解析:

这个函数其实很好理解,就是将更新队列中的watcher拿出来并依次调用他们的callback,但重点在于为什么在for循环之前先对这些watcher进行了升序排列呢? 这处于2点考虑。

  • 确保watcher的更新顺序与它们被创建的顺序一致。

    • 对于父子组件来说,组件的创建顺序是父组件先被创建,然后子组件再被创建,而renderWatcher是在组件实例创建过程中被一同创建的,因此父组件的renderWatcher的id是小于子组件的

    • 对于用户自定义watcher和renderWatcher来说,某个组件的用户自定义watcher是先于组件的renderWatcher被创建的,因此用户自定义watcher的id小于renderWatcher的id。

  • 一旦在更新循环的执行过程中父组件所引用的某个子组件被销毁,则会跳过子组件的watcher。

对于 Watcher 的详细文字描述:

描述来源:blog.csdn.net/weixin_4059…

Watcher 分为三种:

  • Computed Watcher;
  • 用户 Watcher (监听器);
  • 渲染 Watcher

渲染 Watcher 的创建时机:src/core/instance/lifecycle.js。

渲染 watcher 创建的位置: lifecycle.js 的 mountComponent 函数中。

Watcher 是没有静态方法的,因为 $watch 方法中要使用 Vue 的实例。创建顺序:计算属性 Watcher、用户 Watcher (监听器)、渲染 Watcher

Watcher 创建的工作流程:

首先 Watcher 的构造函数初始化,处理 expOrFn (渲染 watcher 和监听器处理不同)。 =>调用 this.get(),它里面调用 pushTarget() 然后 this.getter.call(vm, vm) (对于渲染 watcher 调用 updateComponent),如果是用户 watcher 会获取属性的值 (触发 get 操作)。

当数据更新的时候,dep 中调用 notify() 方法。 =>notify() 中调用 watcher 的 update() 方法。 => 在update() 中调用 queueWatcher()。 => queueWatcher() 是一个核心方法,去除重复操作,调用 flushSchedulerQueue() 刷新队列并执行 watcher。

并且在flushSchedulerQueue() 中对 watcher 排序,遍历所有 watcher,如果有 before,触发生命周期的钩子函数 beforeUpdate,执行 watcher.run(),它内部调用 this.get(),然后调用 this.cb() (渲染 watcher 的 cb 是 noop) 整个流程结束。

查看渲染 watcher 的执行过程

当有数据发生更新时:

  • 首先defineReactive 的 set 方法中调用 dep.notify()。
  • 然后调用 watcher 的 update()
  • 再调用 queueWatcher(),把 watch 存入队列,如果已经存入,不重复添加
  • 接着循环调用 flushSchedulerQueue(),并通过 nextTick(),在消息循环结束之前时调用 flushSchedulerQueue()。
  • 最后调用 watcher.run():通过调用 watcher.get() 获取最新值。如果是渲染 watcher 结束,如果是用户 watcher,调用 this.cb()。

总结:

对于响应式原理中重要的三个函数, Watcher, Observer , Dep 的关系全都梳理完成。而这些也是 Vue 实现的核心逻辑之一。再来简单总结一下三者的关系,其实是一个简单的 观察-订阅 的设计模式, 简单来说就是, 观察者观察数据状态变化, 一旦数据发生变化,则会通知对应的订阅者,让订阅者执行对应的业务逻辑 。我们熟知的事件机制,就是一种典型的观察-订阅的模式

  • Observer, 观察者,用来观察数据源变化.
  • Dep, 观察者和订阅者是典型的 一对多 的关系,所以这里设计了一个依赖中心,来管理某个观察者和所有这个观察者对应的订阅者的关系, 消息调度和依赖管理都靠它。
  • Watcher, 订阅者,当某个观察者观察到数据发生变化的时候,这个变化经过消息调度中心,最终会传递到所有该观察者对应的订阅者身上,然后这些订阅者分别执行自身的业务回调即可 参考