0
点赞
收藏
分享

微信扫一扫

<深入浅出Vuejs>生命周期_初始化阶段

软件共享软件 2022-04-21 阅读 38
vue.js前端

<深入浅出Vuejs>生命周期_初始化阶段


前言


一、Vue构造函数入口

Vue往往是从new开始工作的,因此我们从构造函数开始探索Vue:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue


构造函数逻辑十分简单,进行安全检查后(非生产环境调用Vue是否是new出来的,不是则打印警告信息),调用this._initoptions)来执行生命周期的初始化阶段。
在这之后有五个Mixin方法,执行的时候传入构造函数Vue本身,主要用于在不同的阶段给Vue原型链上添加不同的方法。

  1. initMixin:
  • 合并配置
  • 初始化组件实例关系以及内部属性:比如当前组件的父组件(parent)、根组件(parent)、根组件(parent)、根组件(root)、子组件数组(children)容器、children)容器、children)容器、refs以及内部属性等。
  • 初始化自定义事件
  • 初始化组件插槽信息
  • 调用生命周期函数beforeCreate
  • 初始化组件的 inject 配置项
  • 初始化状态。分别初始化 props、methods、data、computed、watch(划重点!!!)
  • 初始化组件的 provide 配置项
  • 调用生命周期函数create
  • 如果是根组件(自定义配置上有el选项)则调用$mount自动挂载
  1. stateMixin:
    构造函数Vue原型链上初始化$data$props属性,初始化$set$delete$watch方法
  2. eventsMixin:
    构造函数Vue原型链上初始化$on$once$off$emit方法
  3. lifecycleMixin:
    构造函数Vue原型链上初始化_update$forceUpdate$destroy方法
  4. renderMixin:
    构造函数Vue原型链上初始化$nextTick_render方法

那么,_init方法在哪里定义?内部如何实现呢?

二、initMixin&_init

initMixin函数用于将_init挂载到Vue函数的原型链上,_init中执行具体的初始化逻辑

_init()

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
	const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
    // 如果是Vue的实例,则不需要被observe
    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      // 合并配置接下来讨论,resolveConstructorOptions用于获取当前实例的options与父实例的options属性合并成新的options赋值给$options
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    // 第二步: renderProxy
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 第三步: vm的生命周期相关变量初始化
    initLifecycle(vm)
    
    // 第四步: vm的事件监听初始化
    initEvents(vm)
    // 第五步: vm的编译render初始化
    initRender(vm)
    // 第六步: vm的beforeCreate生命钩子的回调,在此之前完成了initlifecycle initevents initrender
    callHook(vm, 'beforeCreate')
    // 第七步: vm在data/props初始化之前要进行绑定
    initInjections(vm) // resolve injections before data/props
    
    // 第八步: vm的sate状态初始化
    initState(vm)
    // 第九步: vm在data/props之后要进行提供
    initProvide(vm) // resolve provide after data/props
    // 第十步: vm的created生命钩子的回调
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    /*
    *如果实例化vue有el选项,则开启模板编译与挂载阶段
    *如果没有el选项则不进入下一个阶段,用户通过$mount方法手动进入模板编译与挂载
    */
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

mergeOptions(合并配置)

配置合并就是将Vue默认与用户自定义的options进行合并后返回一个新的Object,并赋值给vm的属性$options

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)

它接受三个参数:

  1. resolveConstructorOption函数返回的Vue默认配置
  2. 用户自定义的默认配置。若无则传空对象
  3. 当前实例本身

让我们来看一下resolveConstructorOption函数:

//默认形况下,Ctor其实就是Vue构造函数
export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  // 首先需要判断该类是否是Vue的子类
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    // 来判断父类中的options 有没有发生变化
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        // 当为Vue混入一些options时,superOptions会发生变化,此时于之前子类中存储的cachedSuperOptions已经不相等,所以下面的操作主要就是更新sub.superOptions
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

initLifecycle

export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

  • 定义一个常量options指向vm.$options;
  • 搜索当前实例的父实例,如果没有父实例,则给当前实例的$parent设置为undefined,比如当前组件是new出来的,那么就是根组件,父辈肯定是undefined了;
  • 如果当前parent存在且不是抽象组件,则将当前实例添加到父组件的$children中,成为他的孩子之一;
  • 找到当前组件的根组件$root,其实就是父组件的根组件。为何这样就认为是根组件了呢?因为组件是树状关系,每一个“出生”的孩子在出生的时候就认准了自己的祖先。
  • 接下来就是定义子组件容器$ children、$refs等,都是与生命周期有关的数据。

initEvents

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

初始化事件就是指将父组件中使用v-on注册的事件添加到子组件的事件系统中。

如果v-on定义在平台标签上(如DOM),则事件被注册在浏览器事件中

  • 初始化自定义事件,开始在实例上定义了一个内部原型为null的空对象,用来存储事件。
  • 然后又增加一个内部属性_hasHookEvent,初始值为false。之后是判断$ options上是否含有_parentListeners,若存在则执行updateComponentListeners方法。(在模板编译阶段,解析到子组件标签时会实例化子组件,同时将标签上注册的事件解析成object,这些事件会保存在vm.$options._parentListeners中
  • updateComponentListeners方法:循环——parentListeners并使用v-on注册到this._event中即可。

initState

我们经常使用的属性,包括像是data,props,methods,watch,computed等都是在这个方法中进行初始化的。该方法介于beforeCreate和created两个钩子之间,所以在beforeCreate的时候我们还无法访问到Vue实例上的这些属性.

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)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
  1. 首先新增属性_watchers,用来保存组件中所有的watcher.
  2. 判断是否存在props,methods,data等属性,如果有就调用相应的方法进行初始化
  3. 如果没有data属性就使用observe观察空对象
  4. 判断是否存在computed属性,如果有就调用相应的方法进行初始化
  5. 判断是否存在watch属性,如果有就调用相应的方法进行初始化

initProps

function initProps (vm: Component, propsOptions: Object) {
  // 存放父组件传入子组件的props
  const propsData = vm.$options.propsData || {}
  // 存放经过转换后的最终的props的对象
  const props = vm._props = {}
  // 一个存放props的key的数组,就算props的值是空的,key也会存在里面
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // 如果不是跟组件就不需要将数据转换成响应式的
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    // 校验props,包括对类型的校验以及产生最后的属性值
    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
        )
      }
      // 将props变成可响应的,非生产环境中,如果用户修改props,发出警告
      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)
    }
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

首先遍历propsOptions(options中的props),将key字段push到keys数组里,然后通过validateProp方法对prop进行校验,主要是对prop的类型校验,以及判断父组件是否有传入相应的值,如果没有,则查看子组件中有没有声明default属性,有则将default产生的值作为props的value。
在校验完props之后就是使用defineReactive将prop变成可响应的,这样当prop发生变化的时候,对应的依赖组件可以同步变化。在非生产环境中,如果修改prop的值则会发出警告。

initMethods

该方法比较简单

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 $.`
        )
      }
    }
    // 把每个methods[key]绑定到vm上,并将methods[key]的this指向vm
    vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
  }
}

这个方法就是遍历一遍option中的methods并把每一个method绑定到当前vue实例上,修改这些函数的this指针指向vue实例。

initDatas

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)
    }
  }
  //将data转为响应式数据
  observe(data, true /* asRootData */)
}

将对data中的每一个属性进行判断,包括判断属性是否存在methods中和属性是否存在props中,如果都不存在,则将data[key]使用proxy代理到vm上。

initComputed

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)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}

initWatch

watch原理比较简单,就是新建一个watcher,并将watcher放入到对应数据的Observer的Dep中就可以了,这样当监听的数据变化时,就会通知我们的Watcher执行我们传入的函数。

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
     //遍历传入的watch对象,通过createWatcher创建一个个的watcher。
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

让我们来看看createWatcher的代码:

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

createWatcher就是对hander的一些判断(字符串 函数 对象),如果是函数则不做任何处理,是对象则将options设置为handler,并将handler设置为handler对象的handler方法,如果是字符串则从vue实例中取出方法设置成handler。然后使用$watch方法监听数据,并且执行handler。

Vue.prototype.$watch = function (
    expOrFn,
    cb,
    options
  ) {
    var vm = this;
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {};
    options.user = true;
    var watcher = new Watcher(vm, expOrFn, cb, options);
    if (options.immediate) {
      cb.call(vm, watcher.value);
    }
    return function unwatchFn () {
      watcher.teardown();
    }
  };

总结

本文章仅为本人学习总结,如果有不足还请各位指出!

举报

相关推荐

0 条评论