Vue源码解读--初始化 props methods data watch computed响应式

114 阅读2分钟

1.初始化响应式属性

初始化响应式的函数为initState(vm),我们进入函数中看一下

export function initState (vm: Component) {
  vm._watchers = []
  // 把vm实例上的属性赋值给一个变量,方便后续对属性进行操作
  const opts = vm.$options
  // 初始化props 
  // 数据代理到实例上,支持 this.propsKey 进行访问
  if (opts.props) initProps(vm, opts.props)
  // 初始化methods
  // 1.对 propsKkey 和 methodsKey 进行判重处理, propsKey 的优先级大于 methodsKey
  // 2.数据代理到实例上,支持 this.methodsKey 进行访问
  if (opts.methods) initMethods(vm, opts.methods)
  // 如果实例上有data的话,进行data属性的初始化
  // 否则把data打入observe中,实例上的_data相对于data,因为_默认是私有属性
  if (opts.data) {
    // 初始化data
    // 1.对 data 进行判重, props 和 methods 的属性不能和 data 重复
    // 2.对 data 进行代理, 支持 this.data 的方式访问
    // 3.对 data 进行响应式处理
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始化computed
  if (opts.computed) initComputed(vm, opts.computed)
  // 初始化watch
  // nativeWatch为做火狐浏览器的兼容
  
  // Firefox has a "watch" function on Object.prototype...
  // export const nativeWatch = ({}).watch

  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

initState(vm)中的initProps -----初始化props

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  // 对 props 进行循环遍历
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    // 在开发生产环境进行一些提示
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      // 响应式处理
      defineReactive(props, key, value)
    }
    // vm._props = vm.options.props
    // 代理,支持 this.propsKey 方式去访问 props 属性
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

让我们来看看数据代理做了什么

//数据代理
export function proxy (target: Object, sourceKey: string, key: string) {
  // 添加get方法
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  // 添加set方法
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  // 通过 Object.defineProperty 进行代理
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

initState(vm)中的initMethods -----初始化mthods

function initMethods (vm: Component, methods: Object) {
  // 取到propsKey
  const props = vm.$options.props
  // 遍历 methods 的 key
  // 做了判重处理
  // 如果 props 上的key和 methods 上的key一样
  // propsKey 的优先级大于 methodsKey
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    // 把 methods 中的所有方法赋值到 vue 实例上,支持 this.methodsKey 的方法进行访问
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

initState(vm)中的initData -----初始化data

function initData (vm: Component) {
  // 从配置项取 data 属性
  let data = vm.$options.data
  // 判断如果 data 是函数就表示是子组件的data
  // 不是函数就是根上的 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)) {
      // 代理,支持 this.dataKey 进行访问
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 响应式处理
  observe(data, true /* asRootData */)
}

initState(vm)中的initComputed -----初始化computed

function initComputed (vm: Component, computed: Object) {
  // 创建 一个干净的 watchers 对象和一个 _computedWatchers 的属性
  const watchers = vm._computedWatchers = Object.create(null)
  // 判断是不是SSR(服务端渲染)
  const isSSR = isServerRendering()
  // 遍历 computed 对象
  // computed = { key: computed[key]}
  // key 为函数或者对象的名字
  for (const key in computed) {
    // 不管函数还是对象 都赋值给 userDef
    const userDef = computed[key]
    // 判断 getter 是否是一个函数
    // 函数 执行 userDef 赋值给 getter
    // 对象 执行 userDef.get 赋值给 getter
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    // 环境检测
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }
    // 不是服务端渲染
    // 构造函数生成实例对象
    if (!isSSR) {
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }
    // 判断 key 是不是在 vm 或者 vm的原型上
    // key 不在原型上, vm上没有相同的属性
    if (!(key in vm)) {
      // 定义computed, 参数 Vm 实例, 属性名字, 属性值
      defineComputed(vm, key, userDef)
    }
    // key 在原型上证明data,props,methods提前定义了
    // 不能与data,props,methods的属性相同
    else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  // 判断是不是SSR(服务端渲染)
  // shouldCache --> true
  // 不是SSR ---> true
  const shouldCache = !isServerRendering()
  // 如果是函数
  // const sharedPropertyDefinition = {
  //   enumerable: true,
  //   configurable: true,
  //   get: noop,
  //   set: noop
  // }
  // export function noop (a?: any, b?: any, c?: any) {}
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  }
  // 如果是对象
  else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  // 环境处理
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  // 响应式处理,代理到 Vm 实例上,支持通过 this.ComputedKey 进行访问
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 因为 sharedPropertyDefinition.get 是要一个函数 得返回函数
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 如果在实例上存在 dirty 证明第一次进入或者update了
      // 面试题:computed 和 methods 的区别
      // computed 有缓存就是在这里  dirty 控制缓存

      // update () {
      //   /* istanbul ignore else */
      //   if (this.lazy) {
      //     this.dirty = true
      //   } else if (this.sync) {
      //     this.run()
      //   } else {
      //     queueWatcher(this)
      //   }
      // }
    
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

initState(vm)中的initWatch -----初始化watch

// 初始化watch
// watch:{ xxx(){} ,  xxxx:{ deep:true,handler(nv,ov){} } }
function initWatch (vm: Component, watch: Object) {
  // 遍历 watch, key 名 watch[key] 值
  for (const key in watch) {
    // 保留值
    const handler = watch[key]
    // 判断是不是数组 没遇过  看下面官网
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      // 一般都是函数和对象
      createWatcher(vm, key, handler)
    }
  }
}

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

image.png