vue源码学习之数据响应化

 vue数据响应式

vue数据响应化的代码都在src/core/observer里面。具体实现是在Vue初始化时,会调用initState,它会初始化data,props等,这里着重关注data初始化,initData核心代码是将data数据响应化。

initState 

props、methods、computed、watch初始化,data响应化

//文件位置:/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)
  }
}

 initData

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : 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') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    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
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

observe

observe 方法首先判断 value 是否已经添加了 ob 属性,它是一个 Observer 对象的实例。如果是就直接用,否则在 value 满足一些条件(数组或对象、可扩展、非 vue 组件等)的情况下创建一个 Observer 对象, 并最终返回一个Observer实例。

//core/observer/index.js

export function observe (value, vm) {
  if (!value || typeof value !== 'object') {
    return
  }
  var ob
  if (
    hasOwn(value, '__ob__') &&
    value.__ob__ instanceof Observer
  ) {
    ob = value.__ob__
  } else if (
    shouldConvert &&
    (isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (ob && vm) {
    ob.addVm(vm)
  }
  return ob
}

Observer

遍历data的属性,并根据属性的类型做不同的处理。 Observer 类的构造函数主要做了这么几件事:首先创建了一个 Dep 对象实例(关于 Dep 对象我们稍后作介绍);然后把自身 this 添加到 value 的 ob 属性上;最后对 value 的类型进行判断,如果是数组则观察数组,否则观察单个元素。其实 observeArray 方法就是对数组进行遍历,递归调用 observe 方法,最终都会调用 walk 方法观察单个元素。

walk 方法是对 obj 的 key 进行遍历,通过Object.defineProperty方法让它们拥有 getter、setter 方法,来定义响应式数据,这样一旦属性被访问或者更新,我们就可以追踪到这些变化。

export function Observer (value) {
  this.value = value
  this.dep = new Dep()
  def(value, '__ob__', this)
  if (isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment
    augment(value, arrayMethods, arrayKeys)
    this.observeArray(value)
  } else {
    this.walk(value)
  }
}

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], 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])
    }
  }

defineReactive (obj, key, val)

defineReactive 方法最核心的部分就是通过调用 Object.defineProperty 给 data 的每个属性添加 getter 和setter 方法。当 data 的某个属性被访问时,则会调用 getter 方法,当 Dep.target 不为空时调用 dep.depend 和 childObj.dep.depend 方法做依赖收集。如果访问的属性是一个数组,则会遍历这个数组收集数组元素的依赖。当改变 data 的属性时,则会调用 setter 方法,这时调用 dep.notify 方法进行通知。这里我们提到了 dep,它是 Dep 对象的实例。

export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter ? : ? Function,
  shallow ? : boolean
) {
  const dep = new Dep() // 一个key一个Dep实例
  if (arguments.length === 2) {
    val = obj[key]
  }
  // 递归执行子对象响应化
  let childOb = !shallow && observe(val)
  // 定义当前对象getter/setter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // getter被调用时若存在依赖则追加
      if (Dep.target) {
        dep.depend()
        // 若存在子observer,则依赖也追加到子ob
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value) // 数组需特殊处理
          }
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      val = newVal // 更新值
      childOb = !shallow && observe(newVal) // 递归更新子对象
      dep.notify() // 通知更新
    }
  })
}

Dep ()

Dep 类是一个简单的观察者模式的实现。它的构造函数非常简单,初始化了 id 和 subs。其中 subs 用来存储所有订阅它的 Watcher,Dep.target 表示当前正在计算的 Watcher,它是全局唯一的,因为在同一时间只能有一个 Watcher 被计算。

export default class Dep {
  static target: ? Watcher; // 依赖收集时的wacher引用
  subs: Array < Watcher > ; // watcher数组
  constructor() {
    this.subs = []
  }
  //添加watcher实例
  addSub(sub: Watcher) {
    this.subs.push(sub)
  }
  //删除watcher实例
  removeSub(sub: Watcher) {
    remove(this.subs, sub)
  }
  //watcher和dep相互保存引用
  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()
    }
  }
}

前面提到了在 getter 和 setter 方法调用时会分别调用 dep.depend 方法和 dep.notify 方法,接下来依次介绍这两个方法。

dep.depend()

depend 方法很简单,它通过 Dep.target.addDep(this) 方法把当前 Dep 的实例添加到当前正在计算的Watcher 的依赖中。

Dep.prototype.depend = function () {
  Dep.target.addDep(this)
}

dep.notify()

notify 方法也很简单,它遍历了所有的订阅 Watcher,调用它们的 update 方法。

至此,vm 实例中给 data 对象添加 Observer 的过程就结束了。

Dep.prototype.notify = function () {
  // stablize the subscriber list first
  var subs = toArray(this.subs)
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

 Vue数据响应化原理

一开始数据通过observe进行响应式,然后会得到一个Observer,Observer根据数据类型的不同,分别调用walk、observeArray方法对数据进行遍历,通过Object.defineProperty方法让它们拥有 getter、setter 方法有了getter和setter以后,会尝试着做依赖收集,把Dep与Watcher串联起来。在初始化组件过程中,会去访问数据,就会调用getter方法,调用addDep方法把Dep与Watcher产生关联(将Dep实例添加到当前计算Watcher依赖中)。当有数据发生更新的时候,会去调用setter方法,通知dep(dep.notify),让Watcher去调用update方法,从而去更新视图。

vue更新操作  

vue在做数据更新时候,尤其对DOM进行操作的时候,批量的异步的操作。怎么实现的呢?首先我们有一个异步队列,我们把所有的观察者所有的Watcher全部push队列中,然后等到每一个循环周期(event loop)到的时候,统一的去执行队列中所有Watcher(每一个Watcher都有一个update函数,它会比较preVnode与vnode diff操作,如果有变化则做DOM操作)的更新,这样就提高了执行效率。

vue在更新DOM时是异步执行的。只要侦听到数据的变化,vue将开启一个队列,并缓冲在同一个时间循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被push到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。然后,在下一个事件循环tick中,vue刷新队列并执行实际(已去重)工作。vue在内部对异步队列尝试使用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout代替。

queueWatcher(this)入队操作,更新时不是立刻去执行DOM操作,而是让当前的Watcher进入队列中 ,异步批量更新

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值