关于vue.js:vue源码分析响应式系统工作原理

33次阅读

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

上一章,咱们讲到了 Vue 初始化做的一些操作,那么咱们这一章来讲一个 Vue 外围概念 响应式零碎
咱们先来看一下官网对 深刻响应式零碎 的解释:

当你把一个一般的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性。并应用 Object.defineProperty 把这些属性全副转为 getter/setter。Object.defineProperty 是 ES5 中一个无奈 shim 的个性。这也就是为什么 Vue 不反对 IE8 以及更低版本浏览器的起因。

上图是 Vue 官网放出的一张图,而且提到外围概念 Object.defineProperty, 那么咱们间接看源码,咱们看到的Object.definePropertydefineReactive函数的外部,而 defineReactive 函数在 walk 函数外部,顺次找到源头是 Observer

./core/observer/index
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data
  /**   * 生成的 Observer 实例上挂载三个属性   * 1. value, 即观测数据对象自身   * 2. dep, 用于依赖收集的容器   * 3. vmCount, 间接写死为 0   */
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // 在观测数据对象上增加__ob__属性, 是 Observer 实例的援用
    // def 相当于 Object.defineProperty, 区别是 dep 里会把__ob__属性设置为不可枚举
    // 须要留神的是, value.__ob__.value 显然就是 value 自身, 这里有一个循环援用
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {this.walk(value)
    }
  }
  // 用于解决对象类型的观测值, 循环所有的 key 都调用一次 defineReactive
  walk (obj: Object) {const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])
    }
  }
  // 对数组的每一项进行监听
  observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])
    }
  }
}

value是须要被察看的数据对象,在构造函数中,会给 value 减少 ob 属性,作为数据曾经被 Observer 察看的标记。如果 value数组 ,就应用observeArray 遍历 value,对value 中每一个元素调用 observe 别离进行察看。如果 value对象 ,则应用walk 遍历 value 上每个 key,对每个key 调用 defineReactive 来取得该 keyset/get控制权。

那么说到如果 value 是数组的话,调用 observeArray 办法遍历数组,开端还调用了 observe 函数,那到底这个函数有什么用呢?咱们来一探到底:

// 用于观测一个数据
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 对于不是 object 或者是 vnode 实例的数据, 间接返回, 不会进行观测
  if (!isObject(value) || value instanceof VNode) {return}
  let ob: Observer | void
  // 如果数据上已有__ob__属性, 阐明该数据曾经被观测, 不再反复解决
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  // 要观测一个数据须要满足以下条件: 
  // 1. shouldObserve 为 true, 这是一个标记位, 默认为 true, 某些非凡状况下会改成 false
  // 2. !isServerRendering(), 不能是服务端渲染
  // 3. Array.isArray(value) || isPlainObject(value), 要观测的数据必须是数组或者对象
  // 4. Object.isExtensible(value). 要观测的数据必须是可扩大的
  // 5. !value._isVue, 所有 vue 实例的_isVue 属性都为 true, 防止观测 vue 实例对象
  } 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 函数的作用是:查看对象上是否有 ob 属性,如果存在,则表明该对象曾经处于 Observer 的察看中,如果不存在,则 new Observer 来察看对象。
回到上文,数组说完了,那么来说对象的函数 walk 调用,咱们看到间接是调用了 defineReactive 函数,那咱们来一探到底:

// 定义响应式对象,给对象动静增加 get set 拦挡办法,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
  if (!getter && arguments.length === 2) {val = obj[key]
  }
  const setter = property && property.set

  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 */
      // 判断 NaN 的状况
      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()}
  })
}

能够见得 defineReactive 函数的作用是: 通过 Object.defineProperty 设置对象的 key 属性,使得可能捕捉到该属性值的 set/get 操作,且 observe 函数深度遍历,所以把所有的属性都增加到了 Observe 下面了,也就是说,咱们对数据的读写就会触发 getter/setter, 再者咱们能够看到get 办法外面有 Dep.target 这个变量,dep.dependdependArrayset办法外面有 dep.notify 这些办法,可想而知,咱们依赖了 Dep 这个文件:

export default class Dep {
  // target 是一个全局惟一的 Watcher
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;
  // 生成每个实例惟一的 uid, subs 用于存储 watcher
  constructor () {
    this.id = uid++
    this.subs = []}

  // 增加一个 watcher
  addSub (sub: Watcher) {this.subs.push(sub)
  }

  // 删除一个 watcher
  removeSub (sub: Watcher) {remove(this.subs, sub)
  }

  // 将本身退出到全局的 watcher 中
  depend () {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()}
  }
}

察看 dep 文件,咱们能够看到一个 Dep 类,其中有几个办法:

  • addSub: 接管的参数为 Watcher 实例,并把 Watcher 实例存入记录依赖的数组中
  • removeSub:addSub 对应,作用是将 Watcher 实例从记录依赖的数组中移除
  • depend: Dep.target上寄存这以后须要操作的 Watcher 实例,调用 depend 会调用该 Watcher实例的 addDep 办法。
  • notify: 告诉依赖数组中所有的 watcher 进行更新操作
    而且发明了一个 subs 用来存储订阅者。
    剖析完了之后,咱们就总结出一句话,dep是一个用来存储所有订阅者 watcher 的对象,他的 notify 办法就是去遍历告诉所有的 Watcher 订阅者数据源产生了扭转须要去更新视图了。
    那么咱们再来看一下 Watcher 的构造是咋样的:
  • 参考 Vue3 源码视频解说:进入学习
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;
  newDepIds: SimpleSet;
  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.lazy = !!options.lazy
      this.sync = !!options.sync
    } 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 = []
    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
        )
      }
    }
    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
  }

  /**   * 接管参数 `dep(Dep 实例)`,让以后 `watcher` 订阅 `dep`   */
  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)
      }
    }
  }

  /**   * 革除 `newDepIds 和 newDep` 上记录的对 dep 的订阅信息   */
  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
  }

  /**   * 立即运行 `watcher` 或者将 `watcher` 退出队列中期待对立 `flush`   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()
    } else {queueWatcher(this)
    }
  }

  /**   * 运行 `watcher`,调用 `this.get()` 求值,而后触发回调   */
  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)
        }
      }
    }
  }

  /**   * 调用 `this.get()` 求值   */
  evaluate () {this.value = this.get()
    this.dirty = false
  }

  /**   * 遍历 `this.deps`,让以后 `watcher` 实例订阅所有 `dep`   */
  depend () {
    let i = this.deps.length
    while (i--) {this.deps[i].depend()}
  }

  /**   * 去除以后 `watcher` 实例所有的订阅   */
  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
    }
  }
}

咱们看到了一个 Watcher 类,并且有一些办法:

  • get:Dep.target 设置为以后 watcher 实例,在外部调用 this.getter,如果此时某个被 Observer 察看的数据对象被取值了,那么以后 watcher 实例将会主动订阅数据对象的 Dep 实例
  • addDep: 接管参数 dep(Dep 实例),让以后watcher 订阅dep
  • cleanupDeps: 革除 newDepIds 和 newDep 上记录的对 dep 的订阅信息
  • update: 立即运行 watcher 或者将 watcher 退出队列中期待对立fresh
  • run: 运行 watcher,调用this.get() 求值,而后触发回调
  • evaluate: 调用 this.get() 求值
  • depend: 遍历 this.deps,让以后watcher 实例订阅所有dep
  • teardown: 去除以后 watcher 实例所有的订阅
    那么咱们晓得这么多办法,来梳理一下流程

咱们的数据发生变化,咱们 data 外面所有的属性都能够看做一个 dep,而dep 外面的 subs 就是寄存以后属性的中央,当咱们数据发生变化的时候就不会被监听到,咱们就要通过 dep 去调用 notify 办法告诉所有的 Watcher 进行更新视图。
那么问题又来了,这个 this.subs 是如何增加订阅者的?

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
  }

  /**   * Add a dependency to this directive.   */
  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)
      }
    }
  }

咱们在 Dep 中能够看到 Dep 在一开始定义了一个全局属性 Dep.target,在新建watcher 是,这个属性为 null,而在watcher 的构造函数中最初会执行本人的 get() 办法, 进而执行 pushTarget(this) 办法:

// 将 watcher 实例赋值给 Dep.target,用于依赖收集。同时将该实例存入 target 栈中
export function pushTarget (_target: ?Watcher) {if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

能够看到 get() 办法,value = this.getter.call(vm, vm),而后 popTarget() 办法:

// 从 target 栈取出一个 watcher 实例
export function popTarget () {Dep.target = targetStack.pop()
}

Dep.target只是一个标记,存储以后的 watcher 实例,触发 Object.defineProperty 中的 get 拦挡,而在 Oject.defineProperty 中的 get 那里,咱们能够看到 dep.depend(),正是在这里将以后的订阅者watcher 绑定当 Dep 上。
也就是说,每个 watcher 第一次实例化的时候,都会作为订阅者订阅其相应的Dep

写到这里,置信各位对 数据响应式 曾经有很粗浅的了解了吧,那么咱们还有一个话题,咱们是如何进行 初始化渲染更新 二次更新视图 的?下章咱们讨论一下。

正文完
 0