Skip to content

vue源码阅读之响应式原理 #4

@tanxchen

Description

@tanxchen

Vue 中对数据的响应式,即对 propsdatacomputed 的变化进行响应式更改。

其入口是在 src/core/instance/init.js 里的 initState(vm) 里进行实现的。

initState(vm) 里 初始化了 props, methods, data, computed, watch

initData

initData 入手来看下:

function initData (vm: Component) {
  let data = vm.$options.data
  // 将_data作为中间变量,后续 proxy 里会用到
  data = vm._data = typeof data === 'function'
    ? getData(data, vm) // getData 做了错误处理,并控制了 Dep.target,使得避免在获取 data 初始值的过程中意外地把依赖记录下来。
    : data || {}
  // 省略相关 warn 代码...
  // 进行 data proxy methods 重复 key 的检验并抛出 warn msg
  // 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]
    // 省略相关关于重复 key 检测 warn 代码
    // 对 _data 对象上的每个 key 进行 getter setter 设置
    // 使得访问 this.xxx 代理到 this._data.xxx
    proxy(vm, `_data`, key)
  }
  // observe data
  /**
   * 响应式核心方法
   * 即 new Observer(value)
   * 具体看 new Observer(value) 逻辑
   * */
  observe(data, true /* asRootData */)
}

initData 里首先将原始 data 复制了份 _data,然后遍历了每个 key,
通过 proxy 方法对 _data 里的 key 进行了 getter setter 设置。

使得访问 this.xxx 代理到 this._data.xxx。

接下来执行 observe(data, true /* asRootData */)

该方法结果是 return new Observer(value),返回了一个 Observer 实例。

Observer

来看下 Observer 类的定义,这也是响应式实现的主要地方。

class Observer 源码
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
    // 添加 __ob__ 属性,值为自身,即 this.data.__ob__ = this
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 对数组原生方法进行了劫持,使得push 等能监控到,来触发响应式,但无法监控到 通过下标更改的情况
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 对数组进行遍历执行 observe
      this.observeArray(value)
    } else {
      // 会进入 walk 方法
      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 方法,设置 getter setter, 真正的响应代码
      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 在 new 的时候,主要逻辑会先执行 this.dep = new Dep() 来创建依赖实例,然后处理数组/对象的情况。

这中间还对里面的每个元素进行了深度遍历,使得每个元素均是响应式的。

再主要来看 this.walk(value) 方法,该方法通过 defineReactive 方法,设置 getter setter,这是真正的响应代码。

defineReactive 源码
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
  // 这里代码类比 computed 里属性定义的 getter setter 那样
  // 定义了,就用定义的 getter setter 方法
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // childObserve 对象,对 child 进行监听
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 这步主要收集依赖,并返回值
    get: function reactiveGetter () {
      // 兼容使用定义的 getter 情况
      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()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      // 自己定义了 setter 就调用自己的 setter, 没有就直接赋值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // newVal 是个 object,则继续递归执行监听 observe
      childOb = !shallow && observe(newVal)
      // 触发更新
      dep.notify()
    }
  })
}

defineReactive 代码可以简化如下:

_defineReactive (obj, key, val) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: true,
    get: function reactiveGetter() {
      dep.addSub(Dep.target)
      return val
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value) return
      // 自己定义了 setter 就调用自己的 setter, 没有就直接赋值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      dep.notify()
    }
  })
}

defineReactive 函数内,通过闭包实例化了 Dep 这个订阅者。

Dep

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

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      // Dep.target 即 watcher:watcher.addDep(dep)
      Dep.target.addDep(this)
    }
  }

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

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
// 保存当前全局唯一存在的 watcher
Dep.target = null
const targetStack = []

export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}

Dep 这里可以简化为:

class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

Dep.target = null

Dep 这个依赖订阅者,有自己的 subs 用来缓存 Dep.targetDep.target 其实是一个 watcher 实例,这里先不管 watcher。
回到 defineReactive,在 get 里将 watcher 实例添加到订阅者 Dep 里,
在 set 时遍历缓存在 dep.subs 数组里的 watcher 实例,执行 watcher.update() 方法,来触发更新。

Watcher

来看下 class Watcher

class Watcher 源码
  export default class Watcher {
    vm: Component; // 实例自身
    expression: string;
    cb: Function;
    id: number;
    deep: boolean;
    user: boolean;
    lazy: boolean;
    sync: boolean;
    dirty: boolean;
    active: boolean;
    deps: Array<Dep>;
    newDeps: Array<Dep>;
    depIds: SimpleSet; // ES6 set 类型
    newDepIds: SimpleSet; // ES6 set 类型
    before: ?Function;
    getter: Function;
    value: any;

    constructor (
      vm: Component,
      expOrFn: string | Function, // 表达式本身 [ getter | noop ]
      cb: Function, // [ noop ]
      options?: ?Object, // { lazy: true } // 如果设置为 true 则在第一次 get 的时候才计算值,初始化的时候并不计算。init 时为 true
      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.lazy = !!options.lazy
        this.sync = !!options.sync
        /*
        before作用是定义 beforeUpdate 钩子:

          before () {
              if (vm._isMounted) {
                callHook(vm, 'beforeUpdate')
              }
            }

        */
        this.before = options.before
      } else {
        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 = []
      // 两个id 为 set 实例,在 add 时候,防止重复添加相同 id
      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 = 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()
    }

    /**
     * Evaluate the getter, and re-collect dependencies.
     */
    // 执行 getter, 并重新收集依赖关系
    get () {
      /* pushTarget 代码:

          Dep.target = null
          const targetStack = []

          export function pushTarget (_target: ?Watcher) {
            if (Dep.target) targetStack.push(Dep.target)
            Dep.target = _target
          }

          export function popTarget () {
            Dep.target = targetStack.pop()
          }

      */
      // 这行代码作用:在进行 get 取值时,使得 Dep.target 为 watcher 实例自身
      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
    }

    /**
     * Add a dependency to this directive.
     */
    addDep (dep: Dep) {
      const id = dep.id
      // if (!this.newDepIds.has(id)) 判断,为了防止如 computed 里
      // `return this.a + this.a` 这种相同值的计算情况
      if (!this.newDepIds.has(id)) {
        this.newDepIds.add(id)
        this.newDeps.push(dep)
        if (!this.depIds.has(id)) {
          dep.addSub(this)
        }
      }
    }

    /**
     * Clean up for dependency collection.
     */
    cleanupDeps () {
      let i = this.deps.length
      while (i--) {
        const dep = this.deps[i]
        if (!this.newDepIds.has(dep.id)) {
          dep.removeSub(this)
        }
      }
      let tmp = this.depIds
      this.depIds = this.newDepIds
      this.newDepIds = tmp
      this.newDepIds.clear()
      tmp = this.deps
      this.deps = this.newDeps
      this.newDeps = tmp
      this.newDeps.length = 0
    }

    /**
     * Subscriber interface.
     * Will be called when a dependency changes.
     */
    update () {
      /* istanbul ignore else */
      if (this.lazy) {
        this.dirty = true
      } else if (this.sync) {
        this.run()
      } else {
        queueWatcher(this)
      }
    }

    /**
     * Scheduler job interface.
     * Will be called by the scheduler.
     */
    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) {
            try {
              this.cb.call(this.vm, value, oldValue)
            } catch (e) {
              handleError(e, this.vm, `callback for watcher "${this.expression}"`)
            }
          } else {
            this.cb.call(this.vm, value, oldValue)
          }
        }
      }
    }

    /**
     * Evaluate the value of the watcher.
     * This only gets called for lazy watchers.
     */
    evaluate () {
      this.value = this.get()
      this.dirty = false
    }

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

    /**
     * Remove self from all dependencies' subscriber list.
     */
    teardown () {
      if (this.active) {
        // remove self from vm's watcher list
        // this is a somewhat expensive operation so we skip it
        // if the vm is being destroyed.
        if (!this.vm._isBeingDestroyed) {
          remove(this.vm._watchers, this)
        }
        let i = this.deps.length
        while (i--) {
          this.deps[i].removeSub(this)
        }
        this.active = false
      }
    }
  }

上面说到在 set 某个值的时候,实际上会执行 watcher.update() 方法。来看下这个 update 方法:

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

update 方法,同步情况执行 this.run() ,一般情况是异步,会进入到最后个 else 里,执行 queueWatcher(this)

queueWatcher 里主要代码 nextTick(flushSchedulerQueue),它将 watch 队列放到 nextTick 里统一执行,这也使得连续 set 同个数据两次不同值,不会更新两次视图,提升了一定性能。

computed 的原理

注意 watcher 实例化 constructor 最后行有个 this.value:

this.value = this.lazy
  ? undefined
  : this.get()

这个 value,在 initComputed 的时候,会对 computed 里的每个 key new Watcher(...),所以可以说是 computed 的双向绑定实现即是通过 Watcher 来响应变化。

initComputed 源码函数:
const computedWatcherOptions = { lazy: true }

function 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()

  // 遍历定义在 computed 上的 key
  for (const key in computed) {
    const userDef = computed[key]
    // 处理自定义 getter 情况
    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
      )
    }
    // 非 ssr 情况,对 key 进行 监控,ssr 下就不需要监控了
    if (!isSSR) {
      // create internal watcher for the computed property.
      // 对每个key 设置 Watcher
      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)) {
      // 将 computed 里定义的 key 在实例上添加进行代理,使得可直接通过 this.xx 来访问
      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)
      }
    }
  }
}

从上面传入参数 { lazy: true } 可知,初始化时 value 为 undefined
defineComputed(vm, key, userDef)Object.defineProperty(target, key, sharedPropertyDefinition).
sharedPropertyDefinition 里会拿之前 watcher 过的依赖的值:

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

所以 computed 中的值在读取的时候,拿到的结果即 watcher.value。

总结下 data 响应式流程:

this.xx 代理到 this._data.xx 然后 _data 在 Observer 里定义过 setter getter,在初始 render 时会收集依赖到闭包的 Dep 依赖实例的数组里,在 setter 时会通知所有依赖即每个 watcher 实例进行 update 更新。

总结下 computed 响应式流程:

this.xx 初始化时会先创建一个 watcher,然后通过 Object.defineProperty 进行 setter,getter 设置,其中 getter 会得到是 watcher.value 的值。

注意:

Observer 文件下定义了个 arrayMethods,该方法重写了数组方法,使得改变 push 等操作也能触发更新。

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) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

附:
实现vue精简版响应式代码

Metadata

Metadata

Assignees

No one assigned

    Labels

    Vueabout Vue.js

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions