Vue的双向数据绑定

130 阅读1分钟

版本:@2.6.10

initData

在 new Vue 的过程中,有一步initData过程是将data数据变为可观察数据。

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 */)
}
##---流程拆分
1-在initData中获取 $options中的data数据,但是我们在mergeOptions中对data进行了合并,
  $options.data返回的是一个函数。所以要用genData去获取数据。然后将获取的数据(对象)赋值给 vm._data.

2-之后又对data的类型进行判断,data需要是一个object

3-将 _data 中的值代理到vm上

4-将data变成观察者observer(data, true)

observer(data, true)

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
}
##
1-data需要是object 而且不能是vnode的实例【这个还不懂】
2-当data有 __ob__属性的话,整明data已经被observe,只需要将data.__ob__返回,避免重复观察。
3-当满足
	shouldObserve &&                                   //开关,在initProps时已开启
    !isServerRendering() &&                            //非服务端渲染????
    (Array.isArray(value) || isPlainObject(value)) &&  //data是对象或者数组类型
    Object.isExtensible(value) &&                      //data可扩展
    !value._isVue                                      //data._isVue 为空或者为false ????
	以上五个条件时才对数据进行观察。

ob = new Observer(value)

ob = new Observer( value )

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have 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)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /**
   * Walk through all properties 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])
    }
  }
}
## -- ob实例化属性
ob.value = data
ob.dep = new Dep()
def(data, '__ob__', ob) 【data.__ob__ = ob 将__ob__属性设置为不可枚举,防止重复观察】
__ob__.value又指向data,
__ob__.dep = new Dep()属性【与$set有关】

-- 数组劫持
当data是数组而且对象有__proto__属性时,将数组原型放到data原型上,但是在数组方法上做了一层拦截。
当 使用 push / unshift / splice 传递第三个参数 这些对数组数据进行改变或者新增数组数据的操作时,
将会对新增/修改的数据进行观察。然后再 ob.dep.notify() 让依赖进行更新。

对于对象 我们用 defineReactive 对对象中的每个 key 进行赋能

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()
    }
  })
}  
1-对get进行拦截,Dep.target可能是userWatcher/computedWatcher/renderWatcher,
触发dep的depend函数【通知Dep.target收集dep,Dep.target再通知dep收集Dep.target,进行双向收集。】

ob 属性中dep的作用

#####对于 data.__ob__ 属性中的dep 
假如我有data.a.b属性

observer(data)时 检测data是对象还是数组
ob = new Observer(data)     
data.__ob__ ={value:data,dep:new Dep(),vmCount:0}    return ob
walk(data)  运行  defineReactive()   

defineReactive(data, a)  ==>   dep=new Dep()   val = data['a']    **childOb = observe(val)**  

defineReactive(data, a)是设置的data.a的get,get劫持触发的是data.a闭包保存的dep
childOb = observe(data.a)是设置的
                    data.a.__ob__ ={value:a,dep:new Dep().vmCount:1}    return ob

childOb = data.a._ _ob_ _

当childOb.dep.depend()
是在我们的data.a收集依赖时,data.a.__ob__收集同样的依赖,作用是在我们$set时,
#####

$set(target, key)

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
}
1-先对target的有效性做校验,target 是undefind / null 会报警
2-如果target是array key 也是有效的数组key(整数),就直接对数组进行操作,返回val,这样同样能引起target的变化触发更新
3-当key 属于 target 且key是target的自身属性,相当于修改对象的值,同样触发更新
4-当23情况不满足时,如果是target是data的属性,那么可能是为target添加新的属性,
    取出 ob = target.__ob__ 当ob不存在说明非双向绑定属性,直接赋值,返回。
    ob存在就说明是双向绑定属性,而且要给target添加属性key:value,
    运行defineReactive(ob.value, key, val)观测新的属性,
    ob.dep.notify()  //重点
        在ob.dep中同样收集了和target一样的依赖,我们运行ob.dep.notify()通知依赖target的watcher重新渲染,

Vue.prototype.delete(target,key)Vue.prototype.delete(target, key)和 Vue.prototype.set(target, key)相似