Vue2响应式原理

202 阅读5分钟

目标

  • 形成一个闭环认知,画流程图
  • 总结

响应式原理流程

vue2响应式原理主要涉及使用Object.defineProperty重写数据的setter和getter使其变为响应式数据

initState

路径:src\core\instance\state.ts

在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法,它的定义在 src/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)
  }
}

这里可以看出initState是按顺序对props、methods、data、computed以及watcher等属性做了初始化操作

这里着重介绍一下props以及data

initProps

路径:src\core\instance\state.ts

做了两件事:

  1. 调用defineReactive将每个prop的值变成响应式,可以通过vm._props.xxx访问定义在props中对应的属性
  2. 通过proxy把vm._props.xxx访问代理到vm.xxx上

initData

路径:src\core\instance\state.ts

做了两件事:

  1. 调用oberve方法观测整个data的变化,把data也变成响应式,可以通过vm._data.xxx访问定义在data中对应的属性
  2. 通过proxy把vm._data.xxx访问代理到vm.xxx上

proxy

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

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将对target[sourceKey][key]的读写变成了对target[key]的读写

observe

observe就是用来检测数据的变化,它的定义在 src/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__
  } 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
}

observe将传进来的值进行判断,如果是vnode或者不是对象就不往下进行;如果往下进行,检测value对象是否有__ob__属性,如果没有,则实例化一个observer对象作为该对象的__ob__属性,将__ob__对象里的vmCount属性值加1,最后返回这个__ob__对象

Observer

路径: src/core/observer/index.js

上面observe有个步骤就是实例化Observer对象作为value对象的__ob__属性,而这个observer实际上的行为可以概括为给对象的属性添加getter和setter,并且通过getter和setter来监听属性的变化

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
export class Observer {
  value: any;
  dep: Dep;
  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)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /**
   * Walk through each property 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])
    }
  }
}/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

Observer是一个类,他的构造函数做了这些事情:

  1. 为自己的属性dep实例化一个Dep对象,设置vmCount为0
  2. 并且将自己这个Observer实例作为传进来的value对象的__ob__属性(通过def函数,其实也是对Object.defineProperty的封装)
  3. 然后如果传进来的值是数组,则对这个数组执行observeArray方法,否则,也就是对象,执行walk方法
  4. observeArray是遍历数组且再次对用observe方法(这也就是为什么通过数组下标改变数组的值不会进行响应式)
  5. walk方法是遍历对象的key,对每个key调用defineReactive

defineReactive

defineReactive就是通过Object.Property给对象的属性添加getter和setter

/**
 * Define a reactive property on an Object.
 */
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
  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.depend()
        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()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

在getter和setter中都会判断属性是否原本就有getter和setter,如果有就调用原本的getter和setter,保证不会覆盖之前已经定义的getter/setter

在getter中会进行依赖收集

在setter中会获取新的值,然后重新observe这个新的值,确保响应式,然后通知观察者更新

依赖收集

Dep

路径: src/core/observer/dep.js

dep实际上就是对Watcher的一种管理

dep.depend()触发Dep.target.addDep,Dep.target就是一个Watcher,addDep方法会将dep实例化对象及其id存到newDeps以及newDepIds两个数组中,并调用dep的addSub方法,将该Watcher放入这个数据的dep持有的subs中

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

Watcher

路径: src/core/observer/watcher.js

在Vue挂载的过程中,会调用mountComponent方法,这个方法的核心就是实例化一个渲染Watcher,它的回调函数中会调用updateComponent方法,在此方法调用vm._render方法先生成虚拟Node,最终调用vm._update更新dom。

Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数。

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

而在渲染VNode过程中,就会访问vm上面的数据,这个时候就触发了数据的getter进行数据依赖收集也就是dep.depend()

PS: 以上watcher和dep是概括记录一下今晚所学内容,后续还需详细展开(比如实例化watcher的过程怎么调用updateComponent方法、vm._update以及vm._render等等)

上面讲的都是依赖收集的环节,接下来讲讲派发更新的环节

派发更新

当数据被赋予新的值,会触发数据的setter,setter做了两个关键的事情,第一步是setter对新的值进行observe变成响应式的值;然后第二步进行dep.notify,通知subs里存放的订阅者(Watcher)进行更新。

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // ...
    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()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
       // 对新的值进行observe使新的值也变为响应式对象
      childOb = !shallow && observe(newVal)
      // 通知订阅者队列里的watcher进行视图更新
      dep.notify()
    }
  })
}

通知subs订阅者队列中的watcher进行更新的notify函数,实际就是遍历subs队列,让每一个watcher调用update方法

class Dep {
  // ...
  notify () {
  // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
class Watcher {
update() {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
}

这里对于watcher的不同状态会执行不同的逻辑,在一般的组件更新数据的场景,会走到最后一个queueWatcher(this)的逻辑,queueWatcher是用了一个队列 来保存要进行视图更新的watcher,这个队列也是vue进行派发更新的优点,vue不会在每次数据改变后就触发watcher的回调进行视图更新,而是将这些watcher放入queue这个队列中,调用nexttick以异步的方式调用flushSchedulerQueue

export function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  if (has[id] != null) {
    return
  }

  if (watcher === Dep.target && watcher.noRecurse) {
    return
  }

  has[id] = true
  if (!flushing) {
    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
    while (i > index && queue[i].id > watcher.id) {
      i--
    }
    queue.splice(i + 1, 0, watcher)
  }
  // queue the flush
  if (!waiting) {
    waiting = true

    if (__DEV__ && !config.async) {
      flushSchedulerQueue()
      return
    }
    nextTick(flushSchedulerQueue)
  }
}

那么上面说到flushSchedulerQueue到底是干嘛的呢?接下来我们来看看他的源码,位置在src/core/observer/scheduler.js

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.

  // 1. 对队列做了由小到大的排序,因为父组建的创建过程是先于子的,所以watcher的创建也是先父后子
  // 2. 用户自定义的watcher要优先于渲染watcher的执行;因为用户自定义watcher是在渲染watcher前创建的
  // 3. 如果一个组件在其父组件的watcher执行期间被销毁,那么他对应的watcher都可以跳过,所以父组件的watcher应该先执行
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    ....
}

在flushSchedulerQueue里面,先是对queue队列里的watcher进行排队,按id来从小到大排序,也就是说,父组件的watcher优先执行,然后遍历queue里的每个watcher执行他们的run方法,这个方法就是用于更新视图的,当然我们可以看到这个遍历的长度是queue.length,这个队列长度是会变的,在执行watcher.run()使,用户可能会添加新的watcher,这时就会执行到queueWatcher的else逻辑中:

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      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
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // ...
  }
}

这时flushing为true,是将待插入的watcher插入到id从小到大排序的queue队列中。

我们来看看watcher.run这个直接导致视图更新的方法吧,位置在src/core/observer/watcher.js

run() {
    if (this.active) {
      // 获取当前的值
      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
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(
            this.cb,
            this.vm,
            [value, oldValue],
            this.vm,
            info
          )
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

这里是通过自身的get方法获取当前的值,然后做判断,如果新值不等于旧值,新值是对象或者deep模式这个三个条件满足一个就执行watcher的回调,回调函数里传入了新值和旧值,所以这就是我们自定义的watcher能拿到新值和旧值的原因,而渲染的watcher在调用get方法获取当前值时就会执行getter方法:

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

这就是我们修改数据后会引起视图的更新组件的重新渲染,也就是所谓的响应式,接着就会执行patch的过程,这就涉及到虚拟dom和diff算法

画一个流程图

vue2响应式原理流程图

总结

vue2的数据响应式原理和observer、watcher、dep这个三个类有关,在vue初始化data的时候,会做两件事:

  1. 将this.data.xxx代理到this.xxx
  2. 通过observe方法将data以及data里的数据变为响应式对象

observe会判断传进来的数据是否为对象,如果是对象会给数据附上一个__ob__的属性,这个属性就是一个observer实例化对象,observer会通过数据劫持将数据对象变为响应式对象,数据劫持会重写数据的getter和setter方法,触发数据的getter方法会收集依赖,触发setter方法会通知订阅者更新视图,那这个收集依赖实际上就是调用dep的depend方法,depend的方法会将当前的watcher也就是Dep.target存入自己的一个订阅者队列subs,当触发setter方法时调用dep.notify,这个方法会遍历订阅者队列subs里的watcher,让每个watcher调用update方法来进行更新视图。

update方法会将这些要进行派发更新的watcher放入一个队列中(queue),然后使用nexttick执行flushSchedulerQueue,也就是进行异步更新视图,在flushSchedulerQueue里会按id从小到大排序watcher(也就是父组件的watcher先执行),然后遍历queue里的watcher,依次执行自身的run方法,在run方法中就会调用get方法,也就会触发getter方法,也就是updateComponent方法,从而导致视图更新。

参考

深入理解Vue响应式原理

图解 Vue 响应式原理

纯干货!图解Vue响应式原理