浅析Vue相应式道理(二)

Vue相应式道理之Observer

之前简朴引见了Dep和Watcher类的代码和作用,如今来引见一下Observer类和set/get。在Vue实例后再增加相应式数据时须要借助Vue.set/vm.$set要领,这两个要领内部现实上挪用了set要领。而Observer所做的就是将修正反应到视图中。

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

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

Observer有三个属性。value是相应式数据的值;dep是Dep实例,这个Dep实例用于Vue.set/vm.$set中关照依靠更新;vmCount示意把这个数据当做根data对象的实例数目,大于0时是实例化传入的根data对象。

组织函数接收一个值,示意要视察的值,如许,在Observer实例中援用了相应式数据,并将相应式数据的__ob__属性指向自身。假如被视察值是除数组之外的范例,会挪用walk要领,令每一个属性都是相应式。关于基础范例的值,Object.keys会返回一个空数组,所以在walk内,defineReactive只在对象的属性上实行。假如是被视察值是数组,那末会在每一个元素上挪用工场函数observe,使其相应式。

关于数组,相应式的完成稍有差别。回忆一下在教程数组更新检测里的申明,变异要领会触发视图更新。其详细完成就在这里。arrayMethods是一个对象,保留了Vue重写的数组要领,详细重写体式格局下面再说,如今只需晓得这些重写的数组要领除了坚持原数组要领的功用外,还能关照依靠数据已更新。augment的用处是令value能够挪用在arrayMethods中的要领,完成的体式格局有两种。第一种是经由过程原型链完成,在value.__proto__增加这些要领,优先选择这类完成。部份浏览器不支持__proto__,则直接在value上增加这些要领。

末了实行observeArray要领,遍历value,在每一个元素上实行observe要领。

数组变异要领的完成

实行变异要领会触发视图功用,所以变异要领要完成的功用,除了包含本来数组要领的功用外,还要有关照依靠数据更新的功用。代码保留在/src/core/observer/array.js

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (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
  })
})

模块内,运用arrayProto保留数组原型,arrayMethods的原型是arrayProto,用来保留变异后的要领,methodsToPatch是保留变异要领名的数组。

遍历methodsToPatch,依据要领名来猎取在arrayProto上的数组变异要领,然后在arrayMethods完成同名要领。

在该同名要领内,起首实行缓存的数组要领original,实行上下文是this,这些要领最终会增加到相应式数组或其原型上,所以被挪用时this是数组自身。ob指向this.__ob__,运用inserted指向被插进去的元素,挪用ob.observeArray视察新增的数组元素。末了实行ob.dep.notify(),关照依靠更新。

observe

工场函数,猎取value上__ob__属性指向的Observer实例,假如须要该属性且未定义时,依据数据建立一个Observer实例,在实例化时会在value上增加__ob__属性。参数二示意传入的value是不是是根data对象。只要根数据对象的__ob__.vmCount大于0。

isObject推断value是不是是Object范例,完成如obj !== null && typeof obj === 'object'

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
}

此处能够看出,value与Observer实例ob之间是双向援用。value.__ob__指向ob,ob.value指向value

Vue.set

在Vue实例化今后,假如想为其增加新的相应式属性,关于对象,直接运用字面量赋值是没有结果的。由相应式数据的完成能够想到,这类直接赋值的体式格局,并没有为该属性自定义getter/setter,在猎取属性时不会网络依靠,在更新属性时不会触发更新。假如想要为已存在的相应式数据增加新属性,能够运用Vue.set/vm.$set要领,但要注重,不能在data上增加新属性。

Vue.set/vm.$set内部都是在/src/code/observer/index.js定义的set的函数。

set函数接收三个参数,参数一target示意要新增属性的对象,参数二key示意新增的属性名或索引,参数三val示意新增属性的初始值。

export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    !Array.isArray(target) &&
    !isObject(target)
  ) {
    warn(`Cannot set reactive property on non-object/array value: ${target}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }

  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 不存在ob 申明不是相应式数据
  if (!ob) {
    target[key] = val
    return val
  }
  // 为target增加新属性
  defineReactive(ob.value, key, val)
  // ob.dep现实是target.__ob__.dep
  ob.dep.notify()
  return val
}

函数内部起首推断target范例,非数组或非对象的目的数据是没法增加相应式数据的。

假如是数组,且key是有用的数组索引,更新数组长度,然后挪用变异要领splice,更新对应的值并触发视图更新。假如是对象,且属性keytarget的原型链上且不在Object.prototype上(即不是Object原型上定义的属性或要领),直接在target上增加或更新key

ob指向target.__ob__,假如target是Vue实例或是根data对象(ob.vmCount > 0),则没法新增数据,直接返回。

接着处置惩罚能为target增加属性的状况。不存在ob时,申明不是相应式数据,直接更新target。不然,实行defineReactive函数为ob.value新增相应式属性,ob.value现实指向target,增加以后挪用ob.dep.notify()关照视察者从新求值,ob是Observer实例。

总结一下,set的内部逻辑:

target是数组时,更新长度,挪用变异要领splice插进去新元素即可。

target是对象时:

  • key在除Object.prototype外的原型链上时,直接赋值
  • key在原型链上搜刮不到时,须要新增属性。假如target__ob__属性,申明不是相应式数据,直接赋值。不然挪用defineReactive(ob.value, key, val)视察新数据,同时触发依靠。

Vue.delete

删除对象的属性。假如对象是相应式的,确保删除能触发更新视图。

Vue.delete现实指向deldel接收两个参数,参数一target示意要删除属性的对象,参数二key示意要删除的属性名。

假如target是数组且key关于的索引在target中存在,运用变异要领splice要领直接删除。

假如target是Vue实例或是根data对象则返回,不允许在其上删除属性。key不是实例自身属性时也返回,不允许删除。假如是自身属性则运用delete删除,接着推断是不是有__ob__属性,假如有,申明是相应式数据,实行__ob__.dep.notify关照视图更新。

export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    !Array.isArray(target) &&
    !isObject(target)
  ) {
    warn(`Cannot delete reactive property on non-object/array value: ${target}`)
  }
  // 数组 直接删除元素
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // 属性不在target上
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  // 不是相应式数据
  if (!ob) {
    return
  }
  ob.dep.notify()
}

小结

关于Observer类和set/get的源码已做了简朴的剖析,仔细的读者能够会有一个题目:target.__ob__.dep是什么时刻网络依靠的。答案就在defineReactive的源码中,其网络操纵同样在相应式数据的getter中实行。

至于defineReactive的源码剖析,在后面的文章再做剖析。

参考链接

    原文作者:荡漾
    原文地址: https://segmentfault.com/a/1190000017152953
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞