Vue源码阅读(二):数据响应式与实现

--本文采自本人公众号【猴哥别瞎说】java

这篇文章咱们来详细了解数据响应式的原理与具体实现。react

聊到 Vue 的数据响应,不少人都会对其非侵入式的数据响应系统津津乐道,大概都知道它是经过数据劫持的方式(修改 Object.defineProperty() )来轻量化实现数据响应式。express

所谓轻量化,指的是:Vue 中的数据模型仅仅是一个个普通的 javaScript 对象,当使用者修改它们的时候,对应的页面 UI 会进行更新。无需直接操做 DOM 元素的同时,对于数据模型的状态管理也变得简单明了。数组

原理

回顾上篇文章,咱们讲到了Watcher: 它与Vue组件实例之间是一一对应的关系。说点题外话:实际上啊,在Vue1.x系列的时候,每个响应式变量都会有一个Watcher。开发者发现这样的粒度太细了,因而在Vue2.x的时候,就变成了更高粒度的划分:一个Vue组件实例对应一个Watcher。bash

Vue的数据化响应的具体实现,实际上依赖的还有两个重要成员:Observer 与 Dep。Observer、Dep、Watcher三者之间的关系在 Vue2.x 中可经过下图简单展现:函数

屏幕快照 2019-11-20 上午7.53.04.png-63.9kB

Observer、Dep、Watcher 之间,经过发布-订阅模式的方式来进行交互。在数据初始化的时候,Watcher 就会订阅对应变量的 Dep。当有数据变化的时候,Observer 经过数据劫持的方式,将数据的变动告知 Dep,而 Dep 则会通知有关联关系的 Watcher 进行数据更新。正如上一节课讲到的,Watcher的notify 过程当中调用了 updateComponent,其包含了两个重要步骤:render 与 update。这两个步骤最终会更新真实页面。post

在一个 Vue 组件实例中,Watcher 只有一个。而实例中的每个响应式变量都会有一个 Dep。因而,一个组件中的 Watcher 与 Dep 之间的关系,是一对多的关系。ui

而现实应用中,Vue 组件确定不止一个啊。组件内部还会嵌套组件,而响应式变量有可能会与多个组件产生关联。因而,在这个层面上,Dep 会对应多个 Watcher。this

综上,Watcher 与 Dep 之间,是多对多的关系。spa

源码解析

咱们的目标是:尝试经过阅读源码的方式,将整个知识点串起来。

沿着上节课的引子,咱们能够在src/core/instance/init.js文件的initState()函数中查看:

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/coumputed。咱们此时只关注 data 部分。留意到observe()方法,进入src/core/observer/index.js(与数据响应式相关的代码都是src/core/observer/内)可知:

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

能够看到,observe()的做用就是返回一个 Observer 对象。因而重点来了:

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  //值得留意:Observer对象在一个Vue组件实例中存在多个,取决于data数据嵌套了几个Object对象或数组
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    //若是是数组
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      //若是是对象
      this.walk(value)
    }
  }
复制代码

只看是对象的状况,因而进入到walk()方法:

walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
复制代码

这里就能够知道,Observer 的做用就是:针对 data 对象的每个属性,分别对其进行数据响应化处理。值得留意:Observer 对象在一个 Vue 组件实例中存在多个,取决于 data 数据嵌套了几个 Object 对象或数组。

Observer 是如何与 Dep、Watcher 关联起来的?咱们先来看看 Dep、Watcher 长啥样子,而后再来进入到最核心的defineReactive()

Dep

看看 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.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()
    }
  }
}
复制代码

从内部变量属性可知,其包含的静态变量 target 是一个 Watcher,其包含的常规变量 subs 是 Watcher 数组。其内部主要的两个方法:depend()关联对应的 Watcher,notify()通知对应的 Watcher 进行 update 操做。

Watcher

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;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  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
  }

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

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

复制代码

这里对 Watcher 的代码进行了必定的简化。经过声明可知:deps变量是 Dep 数组。其核心方法有这三个:addDeps()与 Dep 之间相互关联,get()调用 updateComponent 方法,update()执行批量更新操做。

Dep 中的 subs 为 Watcher 数组,Watcher 中的 deps 为 Dep 数组。也验证了以前的描述:

Watcher与Dep之间,是多对多的关系。
复制代码

defineReactive

此刻,咱们进入到最核心的defineReactive()

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  //一个key 一个dep(),一一对应
  const dep = new Dep()

  ...//忽略

  //val若是是对象或者数组,会递归observe
  //每个对象或数组的出现,都会出现一个新的Observer
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      //
      //值得注意的是,在initState的时候,并无触发Dep.target,由于尚未Watcher生成,
      //Watcher的产生是在第一次$mounted的过程当中生成的
      //而之后每次触发Dep.pushTarget的时候,都会将Dep.target再次被引用到具体的Watcher
      //好比:
      //   watcher.js中的 get()
      //     state.js中的 getData()
      // lifecycle.js中的 callHook()
      if (Dep.target) {
        //depend()是相互添加引用的过程
        //一个Vue实例只有一个Watcher,一个key就有一个Dep
        //在单一Vue组件实例中,Watcher与Dep之间,是一对多的关系
        //考虑到Vue实例存在嵌套(或用户手写了watch表达式),Dep中会保存多个Watcher(存在subs数组中)
        //这样,当key发生变化时,对应的Watcher的notify()方法就会被触发,对应Vue实例就会更新页面
        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
      ...//忽略
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      //特别处理:若是最新赋值是对象,该对象仍然须要响应化处理
      childOb = !shallow && observe(newVal)
      //Dep通知更新
      dep.notify()
    }
  })
}
复制代码

从代码可知:

  1. 每个 data 变量都会有一个 Dep。
  2. get data 变量的时候,会触发dep.depend(),将 Dep 与 Watcher 之间进行关联。
  3. set data 变量的时候,会触发dep.notify(),通知 Dep 对应的 Watcher 进行对应的更新操做。

关联上节课讲到的,Watcher 更新的过程会触发 updateComponent,因而会从新执行$._render()函数与$._update()函数,生成虚拟 DOM,进而更新真实 DOM 操做。

因而,这个针对对象的数据响应化过程,就基本走通了。在下一文章中,咱们来看看针对数组的数据响应化过程是怎样的,它与对象的响应化过程有何不一样?

相关文章
相关标签/搜索