vue源码(七) state.js data、props、watch、computed、methods属性的初始化和响应化

今日目标:state.js

路径:src\core\instance\state.js

在这里插入图片描述

今天就分析这十三个函数,任务艰巨!

首先,有一个共享属性定义

// 共享属性定义
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop, //空函数
  set: noop
}

proxy

作用:做一层代理,将app.data.name变成app.name这种类型,并会给其添加共享属性

// 代理 app.data.name 变成app.name
// proxy(app, 'data', 'name')
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给app.name加上共享属性
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

initState

作用:按顺序初始化props、methods、data、computed、watch这些属性

// 初始化一些data props methods那些 
export function initState (vm: Component) {
  // 初始化watchers数组 观察者队列
  vm._watchers = []
  // 获取选项
  const opts = vm.$options
  // 初始化props
  if (opts.props) initProps(vm, opts.props)
  // 初始化methods
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化data
  if (opts.data) {
    // 如果存在,直接InitData 
    initData(vm)
  } else {
    // 如果不存在data,直接进行observe,true作为根的data observe:todo
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始化computed
  if (opts.computed) initComputed(vm, opts.computed)
  // 初始化watch
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

initProps

对props进行初始化,完成代理和响应化,因此props是响应式的

// 初始化props 
function initProps (vm: Component, propsOptions: Object) {
  // 获取props数据
  const propsData = vm.$options.propsData || {}

  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // 缓存prop keys以便以后props更新可以使用数组迭代
  // instead of dynamic object key enumeration.
  // 而不是动态 object.key枚举
  const keys = vm.$options._propKeys = []
  // 是否是根 如果不存在父节点 就是根
  const isRoot = !vm.$parent
  // root instance props should be converted
  // 根实例的props需要被响应式
  // 如果不是根
  if (!isRoot) {
    // 则不会添加监听观察者
    toggleObserving(false)
  }
  // propsOptions是传入的options.props,也就是选项中的props属性
  // 遍历props属性的key
  for (const key in propsOptions) {
    // 将key放进数组,容易迭代
    keys.push(key)
    // 判断prop.type是否是Boolean或String,如果不是,则getPropDefaultValue
    // 获取默认值,并给value添加value._ob_属性,添加到观察者模式中
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    // 忽略
    if (process.env.NODE_ENV !== 'production') {
      // 驼峰转换 vOn v-on
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as compone
          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 {
      // 通过defineProperty的set方法去notify()通知订阅者subscribers有新的值修改
      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.
    // 静态props已经在组件的原型上代理了
    // 在Vue.extend()期间. 我们只需要代理
    // 在这里实例化定义的key。
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  // 可加入观察者模式
  toggleObserving(true)
}

initData

作用:初始化data
1、代理,用上面的proxy函数将将data的所有key代理到vm实例上
2、observe(data, true /* asRootData */) 将data作为根data进行observe响应化,因此data也是响应式的,当然是事先就有的data

// initData 初始化data 接收组件实例
// 做了两件事:1、代理,将data的所有key代理到vm实例上
//           2、observe(data, true /* asRootData */)
function initData (vm: Component) {
  // 获取到选项中的data data可能是对象可能是函数 取决于根
  let data = vm.$options.data
  // 如果data是一个函数 执行getData,如果是对象就是根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
  // 获取data所有属性 准备进行代理
  const keys = Object.keys(data)
  // 获取props,因为props在data之前先初始化
  const props = vm.$options.props
  // 获取methods,因为methods在data之前先初始化
  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)) {
      // 执行代理函数,将data的所有key全部挂到vm上,可以直接vm.获取
      // data:{foo: 'foo'}  vm.data.foo  vm.foo
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 将data作为根data进行observe
  observe(data, true /* asRootData */)
}

getData

作用:获取数据时禁用dep收集,这涉及依赖收集,之后会说

// 转换数据 如果数据
export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  // 调用数据获取程序时禁用dep收集
  pushTarget()
  try {
    // 执行传入的函数 获取数据
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    // 最后禁用dep收集
    popTarget()
  }
}

initComputed

作用:初始化computed 这里面就进行了getter的默认写法,并为computed创建watcher观察者,所以computed也是响应式的

组件定义的computed已在组件原型上定义,我们只需要定义已在这里定义实例化的computed

如果computed在data或props中,警告

// 初始化计算属性
function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  // 创建新的监听空对象
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  // computed属性只是SSR期间的getter
  const isSSR = isServerRendering()
  // 遍历computed的key属性
  for (const key in computed) {
    // 每个值
    const userDef = computed[key]
    // 如果是函数 就默认,不是就获取get computed的get默认写
    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
      )
    }
    // 如果不是ssr渲染
    if (!isSSR) {
      // create internal watcher for the computed property.
      // 为计算属性创建wathcer。
      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.
    // 组件定义的计算属性已在组件原型上定义
    // 我们只需要定义已在这里定义实例化的计算属性
    // 如果computed 属性key 不在虚拟dom中
    if (!(key in vm)) {
      // 定义computed 并将key加入到对象监听中
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) { 
        // 如果key在data中,警告
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        // 如果key在props中,警告
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

defineComputed

作用:对computed的key进行判断,给对应的createComputedGetter或createGetterInvoker方法

//定义计算属性 并且 把属性的数据 添加到对象监听中
export function defineComputed (
  target: any, //目标
  key: string, //属性
  userDef: Object | Function //key的值
) {
  // 是否是ssr 是浏览器
  const shouldCache = !isServerRendering()
  // 如果值是函数
  if (typeof userDef === 'function') {
    // 共享属性.get 
    sharedPropertyDefinition.get = shouldCache
    // 如果不是ssr  createComputedGetter创建计算属性并收集依赖响应式
      ? createComputedGetter(key)
      : createGetterInvoker方法(userDef)
    // .set
    sharedPropertyDefinition.set = noop
  } else {
    // 如果值不是函数
    // 值中是否有get,如果有,判断如果不是ssr并且有缓存 那么sharedPropertyDefinition.get = createComputedGetter(
    // 值中如果没有get,直接sharedPropertyDefinition.get = noop
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
      // .set
    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
      )
    }
  }
  // 添加属性
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

createComputedGetter

作用: 放回computedGetter 创建计算属性 获取值 收集dep依赖

// 放回computedGetter 创建计算属性 获取值 收集 dep 依赖
function createComputedGetter (key) {
  return function computedGetter () {
    // Watcher 实例化之后的对象
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        //为Watcher 添加 为Watcher.newDeps.push(dep); 一个dep对象
        //循环deps 收集 newDeps dep 当newDeps 数据被清空的时候重新收集依赖
        watcher.depend()
      }
      // 返回值
      return watcher.value
    }
  }
}

createGetterInvoker

// 返回computedGetter
function createGetterInvoker(fn) {
  return function computedGetter () {
    return fn.call(this, this)
  }
}

initMethods

作用: 初始化Methods 并进行代理

// 初始化Methods 代理
function initMethods (vm: Component, methods: Object) {
  // 获取props
  const props = vm.$options.props
  // 遍历methods的属性key
  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
        )
      }
      //判断key是否是改对象实例化的
      //如果属性中定义了key,则在methods中不能定义同样的key
      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 $.`
        )
      }
    }
    // 把事件放在最外层对象中,如果是函数为空则给一个空函数,如果是有函数则执行改函数
    // 给最外层一个相同key属性,data.methods.sum() 变成data.sum(),代理
    // 如果methods.sum不是函数 给空函数noop
    // 如果是函数,执行该函数
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

initWatch

作用:初始化watch,并给每个key创建监听,因此watch也是响应式

// 初始化watch
function initWatch (vm: Component, watch: Object) {
  // 循环遍历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)
    }
  }
}

createWatcher

// 创建监听
function createWatcher (
  vm: Component, //vm
  expOrFn: string | Function, //key属性
  handler: any, // key属性值
  options?: Object
) {
  // 属性是否是纯对象 
  if (isPlainObject(handler)) {
    // options
    options = handler
    // 对象中的handler 一定是函数或者字符串
    handler = handler.handler
  }
  // 如果值是字符串
  if (typeof handler === 'string') {
    // 就是key 取值 vm 就是Vue 最外层 中的函数
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

stateMixin

作用:实现$set $delete w a t c h watch方法。还定义了只读的 data $props

在src\core\instance\index.js中调用

// 这里主要看 数据绑定 $watch
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.
  // flow在某种程度上与直接声明的定义对象有问题
  // 使用时Object.defineProperty,所以我们必须按程序建立
  // 这里的对象。
  const dataDef = {}
  // 返回this._data 只有get,作为只读属性?
  dataDef.get = function () { return this._data }
  const propsDef = {}
  // 返回this._props 只有get,作为只读属性?
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        // 避免替换根实例$data
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      // 警告只读
      warn(`$props is readonly.`, this)
    }
  }
  // 给vue原型定义$data属性 
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  // 给vue原型定义$props属性
  Object.defineProperty(Vue.prototype, '$props', propsDef)
  // $set方法 :todo 添加一个数组数据或对象数据
  Vue.prototype.$set = set
  // $delete方法 :todo 删除一个数组数据或对象数据
  Vue.prototype.$delete = del
  // $watch :todo
  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
    // 实例化一个watcher 观察者
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 如果
    if (options.immediate) {
      try {
        // 触发回调
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    // 卸载watcher 观察者
    return function unwatchFn () {
      // 从所有依赖项的订阅方列表中删除self。
      watcher.teardown()
    }
  }
}

总结

initState干了很多事情,按顺序依次初始化props,methods,data,computed,watch这些属性,并且初始化后props,data,computed,watch都是响应式的,都是被加入到依赖中了,当值发生改变时,会触发dep.notify通知观察者watcher中的update执行。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_46299172/article/details/107781914