text: /* @flow */
  
  import config from '../config'
  import Watcher from '../observer/watcher'
  import { pushTarget, popTarget } from '../observer/dep'
  import { isUpdatingChildComponent } from './lifecycle'
  
  import {
    set,
    del,
    observe,
    defineReactive,
    toggleObserving
  } from '../observer/index'
  
  import {
    warn,
    bind,
    noop,
    hasOwn,
    hyphenate,
    isReserved,
    handleError,
    nativeWatch,
    validateProp,
    isPlainObject,
    isServerRendering,
    isReservedAttribute
  } from '../util/index'
  
  const sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
  }
  // 将target[key]代理到target[sourceKey][key]上，主要实现将vm.data[key]代理到vm._data[key]上的操作
  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)
  }
  
  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) // 初始化data
    } else {
      observe((vm._data = {}), true /* asRootData */)
    }
    if (opts.computed) initComputed(vm, opts.computed)
    if (opts.watch && opts.watch !== nativeWatch) {
      initWatch(vm, opts.watch)
    }
  }
  
  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)
    }
    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 (vm.$parent && !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)
      }
      // static props are already proxied on the component's prototype
      // during Vue.extend(). We only need to proxy props defined at
      // instantiation here.
      if (!(key in vm)) {
        proxy(vm, `_props`, key)
      }
    }
    toggleObserving(true)
  }
  
  function initData(vm: Component) {
    let data = vm.$options.data
    data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} // 将$options中的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
    // 遍历并检查_data的key是否和methods及props重名
    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)) {
        // 若key不是vue的保留字($及_开头的默认都是vue保留字，所以vue中申明状态时，不能以$及_开头)
        // 代理将vm[key]代理到vm[_data][key]上
        proxy(vm, `_data`, key)
      }
    }
    // observe data;观测数据
    observe(data, true /* asRootData */)
  }
  
  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()
    }
  }
  
  const computedWatcherOptions = { computed: true }
  
  function initComputed(vm: Component, computed: Object) {
    // $flow-disable-line
    const watchers = (vm._computedWatchers = Object.create(null))
    // computed properties are just getters during SSR
    const isSSR = isServerRendering()
  
    for (const key in computed) {
      const userDef = computed[key]
      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) {
        // create internal watcher for the computed property.
        watchers[key] = new Watcher(
          vm,
          getter || noop,
          noop,
          computedWatcherOptions
        )
      }
  
      // component-defined computed properties are already defined on the
      // component prototype. We only need to define computed properties defined
      // at instantiation here.
      if (!(key in vm)) {
        defineComputed(vm, key, userDef)
      } 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)
        }
      }
    }
  }
  
  export function defineComputed(
    target: any,
    key: string,
    userDef: Object | Function
  ) {
    const shouldCache = !isServerRendering()
    if (typeof userDef === 'function') {
      sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
        : userDef
      sharedPropertyDefinition.set = noop
    } else {
      sharedPropertyDefinition.get = userDef.get
        ? shouldCache && userDef.cache !== false
          ? createComputedGetter(key)
          : userDef.get
        : noop
      sharedPropertyDefinition.set = userDef.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
        )
      }
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
  }
  
  function createComputedGetter(key) {
    return function computedGetter() {
      const watcher = this._computedWatchers && this._computedWatchers[key]
      if (watcher) {
        watcher.depend()
        return watcher.evaluate()
      }
    }
  }
  
  function initMethods(vm: Component, methods: Object) {
    const props = vm.$options.props
    for (const key in methods) {
      if (process.env.NODE_ENV !== 'production') {
        if (methods[key] == null) {
          warn(
            `Method "${key}" has an undefined value 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 $.`
          )
        }
      }
      vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
    }
  }
  
  function initWatch(vm: Component, watch: Object) {
    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)
  }
  
  export function stateMixin(Vue: Class<Component>) {
    // flow somehow has problems with directly declared definition object
    // when using Object.defineProperty, so we have to procedurally build up
    // the object here.
    const dataDef = {}
    dataDef.get = function() {
      return this._data
    }
    const propsDef = {}
    propsDef.get = function() {
      return this._props
    }
    if (process.env.NODE_ENV !== 'production') {
      dataDef.set = function(newData: Object) {
        warn(
          'Avoid replacing instance root $data. ' +
            'Use nested data properties instead.',
          this
        )
      }
      propsDef.set = function() {
        warn(`$props is readonly.`, this)
      }
    }
    Object.defineProperty(Vue.prototype, '$data', dataDef)
    Object.defineProperty(Vue.prototype, '$props', propsDef)
  
    Vue.prototype.$set = set
    Vue.prototype.$delete = del
  
    Vue.prototype.$watch = function(
      expOrFn: string | Function,
      cb: any,
      options?: Object
    ): Function {
      const vm: Component = this
      if (isPlainObject(cb)) {
        return createWatcher(vm, expOrFn, cb, options)
      }
      options = options || {}
      options.user = true
      const watcher = new Watcher(vm, expOrFn, cb, options)
      if (options.immediate) {
        cb.call(vm, watcher.value)
      }
      return function unwatchFn() {
        watcher.teardown()
      }
    }
  }
  