【Vue v2.5.17源码学习】data

149 阅读5分钟

初始化

在new Vue() 创建vue示例时,会调用_init()方法,在这个方法里面,会再调用initState方法进行初始化。初始化data的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 */)
}

首先,先获取参数中data的值,在之前的参数合并里,已经把data合并为function,这里再进行一次判断,如果时function类型,就调用getData()方法获取到data。

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

这里就是用call调用data方法并返回值。之所以try catch是因为这是用户写的代码,防止出错。然后对data数据进行校验:

  1. 如果获取到data不是对象类型,在非生产模式下,会报一个异常提醒。
  2. 遍历所有data属性,如果存在于methods或prop中,会分别报异常提醒。它们的定义优先级是prop > data > methods。

如果都没有问题的话,且不是保留键的话,会调用proxy函数把data属性赋值一份到vm._data上。

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

所以,我们平时用的this.a其实是取自this._data.a。(原因待解答)

最后调用observe(data, true /* asRootData */)方法。

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
}

第一个参数是要观测的data对象,第二个参数是代表是否是根数据,这里传的是true。校验:

  1. 如果要观测的data不是对象或是VNode实例,就直接返回。
  2. 如果data还有__ob__且是Observer的实例,说明这个data已经被观测了的,不需要重复观测,直接返回观测后的值就可以了。
  3. A.shouldObserve是否为true,且。
export let shouldObserve: boolean = true

export function toggleObserving (value: boolean) {
  shouldObserve = value
}

B.如果不是SSR,且。!isServerRendering() C.如果是数组或是对象,且。(Array.isArray(value) || isPlainObject(value)) D.对象是可扩展的,且。一个普通的对象默认就是可扩展的,以下三个方法都可以使得一个对象变得不可扩展:

Object.preventExtensions()、Object.freeze() 以及 Object.seal()。Object.isExtensible(value)

F.当前对象不是vue实例。!value._isVue

以上条件都满足,创建Observer实例

ob = new Observer(value)
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 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(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])
    }
  }
}

先执行构造函数,把传入的值赋给Obsever的实例,然后执行一个new Dep()创建一个Dep实例,这个实例不是针对data中单个属性的,而是针对data的,具体作用现在还没搞明白,先写在这里。

把实例化对象的vmCount设置为零,具有作用后面再说。

然后执行def(value, 'ob', this) 方法。

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

第一个参数是data, 三个参数是Observe实例,也就是说通过访问器属性的方法,给data对象添加一个不可枚举的“bo”属性,而属性的值就是创建的Observe实例。执行完毕之后,data对象变成这个样子。

const data = {
  a: 1,
  // __ob__ 是不可枚举的属性
  __ob__: {
    value: data, // value 属性指向 data 数据对象本身,这是一个循环引用
    dep: dep实例对象, // new Dep()
    vmCount: 0
  }
}

最后是判断数据对象是数组还是纯对象,这里是看根对象data,所以是纯属组,但如果是观测跟对象里面的一个数组对象,那么就走数组的分支了。

我们先看纯对象的处理。

 if (Array.isArray(value)) {
   if (hasProto) {
     protoAugment(value, arrayMethods)
   } else {
     copyAugment(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])
  }
}

纯对象的话,会调用walk函数。遍历data对象中的每个属性,再调用defineReactive方法,把data对象和属性名作为参数传入。

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
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  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 */
      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
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

首先,创建一个Dep实例,这个实例就是为每个data属性收集观察者的容器了。然后通过getOwnPropertyDescriptor方法返回属性的属性描述符。如果configurable为false,证明不可设置,直接返回false。

再看下面的逻辑,获取属性原本的get和set方法,如果存在get方法,就不会对该属性进行深度检测,因为这个是用户自己定义的,可以定义任何逻辑,为避免冲突,就不进行监测。但是如果存在set方法,那么不论有没有get方法,都要被监测。因为defineReactive方法会重新定义属性的get和set方法,在set方法中,新的值会重新被观测,这样就矛盾了。所以有set方法,就会进行深度观测。

深度观测的代码:

 let childOb = !shallow && observe(val)

调用过observe方法后,数据变成什么样子了呢?例如:

const data = {
  a: {
    b: 1
  }
}

observe(data)
const data = {
  // 属性 a 通过 setter/getter 通过闭包引用着 dep 和 childOb
  a: {
    // 属性 b 通过 setter/getter 通过闭包引用着 dep 和 childOb
    b: 1
    __ob__: {a, dep, vmCount}
  }
  __ob__: {data, dep, vmCount}
}

接下来就是对属性设置访问器属性的get和set方法。

 Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    },
    set: function reactiveSetter (newVal) {
  })

具体的实现,我们在一个实例中记录吧。

执行

如下实例:

<div>{{ a }}</div>

data () {
  return {
    a: 1
  }
}

当对模板进行编译后,会取a的值,这样就会调用a的get方法。

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
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
  val = obj[key]
}
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
 }

如果a有getter方法就调用getter方法,如果没有,就直接返回在初始化时,传给defineReactive函数的参数val。这里是典型的闭包的应用。getter和val都是在函数定义时声明和赋值的。然后在调用get方法时,又在当前执行环境中获取到了它们的值。

然后判读Dep.target是否有值。那Dep.target又是个什么呢?

我们知道,在数据初始化之后,也就是data的get和set方法都初始化之后,后面会把编译后的渲染函数,然后通过创建Watcher实例对数据进行求值和挂载。求值的函数就是watcher.get()

get () {
	// 把当前渲染函数的watcher赋值给Dep.target
    pushTarget(this)
    let value
    const vm = this.vm
    try {
    // 调用getter方法会取a的值,然后就会触发现在说到的a的get方法,所以这个Dep.target就是指的渲染函数。
      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
  }

watcher.get()方法会把当前的watcher实例赋值给Dep.target,这样在后面触发watcher.getter方法,从而获取a的值的时候,调用的get方法中获取到的值就是Dep.target就是当前watcher。

然后,执行

dep.depend()

同样,dep是通过闭包的方式获取的,其实就是当前这个属性的观察者对象的收集器,下面看看是如何收集的。

// dep.js
depend () {
  if (Dep.target) {
    Dep.target.addDep(this)
  }
}

首先判断有没有需要收集的watcher,有的话调用Dep.target.addDep(this),也就是watcher.addDep()。

 // watcher.js
 constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    // 省略...
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    // 省略...
  }
// watcher.js
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)
    }
  }
}

watcher.addDep()并不是直接把watcher放入到观察者收集器里,而是做了一些放入前的优化工作。

我们知道会有这种场景,

<div>{{ a }}--{{ a }}</div>

data () {
  return {
    a: 1
  }
}

模板在编译计算值的时候,会获取两遍a的值,这样就会调用两次a的get方法,就会触发两次dep.depend(),其实收集一次就够了,所以框架在做真正的收集动作之前,先做了一个判断

if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
  }

判断属性是否已经收集了当前观察者对象,如果收集了,newDepIds中就会存有dep的id,就不会再重复收集了。如果没有收集,就会继续执行下面逻辑:

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

判断depIds中是否已经存在这个dep了,如果不存在,才会最终把当前watcher收集到依赖属性的容器dep里。那么depIds又是什么呢?

上面我们提到,newDepIds是看当次调用watcher.get()方法时,避免重复收集用到的。而depIds则是在不同时候调用watcher.get()时,避免重复收集用到的。比如在数据发生更改了,页面重新渲染,这时触发属性get方法取值,这时又会触发dep.depend()方法。因为在页面初始化渲染时已经把watcher加入到这个变动属性的dep里了,所以在depIds就有值了,所以就不会再调用dep.addSub(this),重复收集了。

那么,又是在什么时候,把这个dep的id放入到depIds里的呢?这需要继续看watcher.get()方法。

 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
}

value = this.getter.call(vm, vm)执行完之后,走到finally里,其中有这么一句:

this.cleanupDeps()
 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
}

看while下方的代码,其实就是把当前watcher.get()获取到的dep放入到了deps中,把dep的id放入到depIds中,然后清空了newDeps和newDepIds。【待补充】

到此为止,当前属性a已经把依赖它数据变化的观察者watcher收集起来了。当我们修改a的值时,就会触发a的set方法。

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()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }

首先,也是利用了闭包,获取到了属性a之前的值,然后比对要赋给a的值和原来的值,是否一致。判断是否一致有这么一个判断:

newVal !== newVal && value !== value

我们知道NaN !== NaN,所以这里就是说新值旧值都是NaN时,证明没有变化,不需要更新,直接返回。

再往下,会对新值进行再次调用observe,这样可以保证新对象也被检测到。

然后在执行

dep.notify()
notify () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

这就遍历出收集到的watcher,对每一个都执行update方法做更新。

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

这里三块逻辑,this.lazy是指的计算属性watcher,第二段是同步更新的watcher,第三段异步更新页面的watcher。

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}

对数组的观测

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)
    }
  }
  // 省略。。。
}

构造器里有判断值是否是数组的逻辑,如果是,执行下面方法:

const augment = hasProto
      ? protoAugment
      : copyAugment
    augment(value, arrayMethods, arrayKeys)
// can we use __proto__?
export const hasProto = '__proto__' in {}

hasProto就是判断浏览器是否支持__proto__,如果支持,调用protoAugment方法,如果不支持,调用copyAugment方法。

function protoAugment (target, src: Object, keys: any) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

其实就是把第二个参数放到value的原型上。那么第二个参数arrayMethods又是啥呢?

// observer/index.js
import { arrayMethods } from './array'

在observer文件夹下的index.js的页面中,引入过这么一句,一下是该文件的全部代码:

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
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
  })
})

arrayProto是数组原型。 arrayMethods是以数组原型为原型,创建出的新对象。 methodsToPatch数组里的所有方法名,都是会修改原数组值的方法名。

下面逻辑,其实就是对methodsToPatch里的方法进行二次改造,在实现原始方法的功能外,加入响应式。

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

用def方法,拦截数组中的每个元素,把元素赋值对应的function,然后再添加到arrayMethods中。对应的function都做了哪些操作呢?

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
}

先执行原始的数组方法original.apply(this, args),然后再取出之前在数组中定义的__ob__对象。因为这些方法涉及到修改原数组,其实就是增删查改。最终我们也仅需把新增的属性进行观测,然后把页面进行更新就ok了。

switch (method) {
  case 'push':
  case 'unshift':
    inserted = args
    break
  case 'splice':
    inserted = args.slice(2)
    break
}
if (inserted) ob.observeArray(inserted)

push和unshift方法都是添加元素,参数就是要添加的元素。而splice是替换的方法,第三个参数才是最终要添加的元素。然后判断如果确实有新增的元素,就调用ob.observeArray方法对新元素进行监控。

// observer/index.js
observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
    observe(items[i])
  }
}
// observer.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  // 省略...
}
// shared/util.js
export function isObject (obj: mixed): boolean %checks {
  return obj !== null && typeof obj === 'object'
}

这里的观测其实就是调用observe方法,就是如果新增的元素typeof是object(数组和对象的typeof都是object),才进行监控。

触发了methodsToPatch数组中的方法,肯定是会改变原数组的,所以要手动调用一下notify方法更新页面

ob.dep.notify()

最后返回结果即可

return result	

整个过程就是在原来的数组方法里加上了对新元素监听和更新页面的逻辑。

然后再返回来:

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)
    }
  }
  // 省略...
}

augment方法执行完后,调用this.observeArray(value)方法,对数组每个元素调用observer方法进行监听。目的是为了防止以下情况:

const ins = new Vue({
  data: {
    arr: [1, 2]
  }
})

ins.arr.push(3) // 能够触发响应
const ins = new Vue({
  data: {
    arr: [
      [1, 2]
    ]
  }
})

ins.arr.push(1) // 能够触发响应
ins.arr[0].push(3) // 不能触发响应

至此,在初始化中,数组的另外处理逻辑已完成。但是在触发属性的get方法里,还有部分相关逻辑:

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

这里有个childOb的条件判断,其中childOb.dep.depend()先不需要关注,是vue.$set()方法中用到的,我们来看下面的逻辑:

 if (Array.isArray(value)) {
    dependArray(value)
  }
// observer/index.js
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}
// dep.js
depend () {
  if (Dep.target) {
    Dep.target.addDep(this)
  }
}

如果是数组,就会遍历数组中的每个元素,然后手动进行依赖的收集。因为数组的元素没有get,set方法,所以只能手动收集。

Vue.$set(target, key, val)

下面来看下Vue.$set(target, key, val)方法。

export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  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
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

以上是Vue.$set()方法的全貌,下面进一步分析:

if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
// shared/util.js
export function isUndef (v: any): boolean %checks {
  return v === undefined || v === null
}
// shared/util.js
export function isPrimitive (value: any): boolean %checks {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    // $flow-disable-line
    typeof value === 'symbol' ||
    typeof value === 'boolean'
  )
}

这里是判断,如果是非生产环境,目标对象如果是基本类型,就进行提示。

if (Array.isArray(target) && isValidArrayIndex(key)) {
  target.length = Math.max(target.length, key)
  target.splice(key, 1, val)
  return val
}
export function isValidArrayIndex (val: any): boolean {
  const n = parseFloat(String(val))
  return n >= 0 && Math.floor(n) === n && isFinite(val)
}

判断目标对象是数组且要设置的key是有效数字,有效数字是指 1、大于等于 0 的整数。

2、在条件一的基础上,这个整数不能是无限的。在源码中条件 n >= 0 && Math.floor(n) === n 保证了索引是一个大于等于 0 的整数,而条件 isFinite(val) 保证了该值是有限的。

如果满足条件,想改变目标数组的长度,取当前长度和参数key中较大的值。然后通过splice方法,对元素进行替换。这个splice方法是已经被处理过的,所以,会被监听到。直接返回结果就可以了。

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
}
if (!ob) {
  target[key] = val
  return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val

获取目标对象的__ob__,如果不存在,证明这个对象是不是响应式的,直接返回值就可以。如果存在就调用defineReactive方法,为新增加的属性添加get和set方法。然后ob.dep.notify()被调用,更新收集的依赖。注意,这里的依赖是__ob__中收集的依赖,那么这些依赖是何时收集的呢?

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
},
childOb.dep.depend()

因为poxy不支持,所以新增的属性无法设置set,get方法,但是既然修改了目标对象,就应该更新依赖及依赖中的数据,怎么办呢?那就利用__ob__。

调用完defineReactive会执行ob.dep.notify()这一句,就是更新ob.dep中所收集的依赖。可是ob.dep中的依赖是何时收集的呢?就是get方法中childOb.dep.depend()这句,手动把依赖添加到ob.dep中了。

Vue.$del(target, key)

export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  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
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

删除的逻辑就简单很多了,是数组的话调用splice方法删除,splice因为重写过所以会触发页面更新,然后返回。如果是对象,就调用delete方法删除就可以了,如果是响应式的对象,再手动调用ob.dep.notify()更新依赖。

以上就是对于data属性的数据劫持加观察者模式的处理。