Vue——Vue初始化

Posted 。思索

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue——Vue初始化相关的知识,希望对你有一定的参考价值。

前言

今天我们来解密下init.ts中的代码内容,并结合 vue 生命周期来分析下 vue 的初始化;

GitHub
github page

内容

init.ts

import config from \'../config\'
import  initProxy  from \'./proxy\'
import  initState  from \'./state\'
import  initRender  from \'./render\'
import  initEvents  from \'./events\'
import  mark, measure  from \'../util/perf\'
import  initLifecycle, callHook  from \'./lifecycle\'
import  initProvide, initInjections  from \'./inject\'
import  extend, mergeOptions, formatComponentName  from \'../util/index\'
import type  Component  from \'types/component\'
import type  InternalComponentOptions  from \'types/options\'
import  EffectScope  from \'v3/reactivity/effectScope\'

// vue 实例id
let uid = 0

export function initMixin(Vue: typeof Component) 
  // 接收实例化传入的参数
  Vue.prototype._init = function (options?: Record<string, any>) 
    //vue 实例
    const vm: Component = this
    // 每个vue实例都有对应的一个实例id
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    // 代码覆盖率测试
    if (__DEV__ && config.performance && mark) 
      startTag = `vue-perf-start:$vm._uid`
      endTag = `vue-perf-end:$vm._uid`
      mark(startTag)
    

    // a flag to mark this as a Vue instance without having to do instanceof
    // check
    // 标记作为vue的实例不必去执行instanceof
    vm._isVue = true
    // avoid instances from being observed
    // 避免实例被observed观察
    vm.__v_skip = true
    // effect scope
    // 影响范围
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = 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.
      // 由于动态选项合并非常缓慢,并且没有一个内部组件选项需要特殊处理。
      // 传入vue实例并进行组件初始化
      initInternalComponent(vm, options as any)
     else 
      // 根组件配置 | 合并参数
      vm.$options = mergeOptions(
        // 解析构造函数参数
        resolveConstructorOptions(vm.constructor as any),
        options || ,
        vm
      )
    
    /* istanbul ignore else */
    // 代码覆盖测试
    if (__DEV__) 
      initProxy(vm)
     else 
      vm._renderProxy = vm
    
    // expose real self
    vm._self = vm
    // 核心的核心
    // 初始化生命周期
    initLifecycle(vm)
    // 初始化事件监听
    initEvents(vm)
    // 初始化渲染
    initRender(vm)
    // 调用生命周期的钩子函数 | beforeCreate
    callHook(vm, \'beforeCreate\', undefined, false /* setContext */)
    // https://v2.cn.vuejs.org/v2/api/#provide-inject
    // 在data/props前进行inject
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    // https://v2.cn.vuejs.org/v2/api/#provide-inject
    // provide 父组件提供的数据
    // inject 子组件进行注入后直接使用
    // 在data/props后进行provide
    initProvide(vm) // resolve provide after data/props
    // 调用生命周期钩子函数 | created
    callHook(vm, \'created\')

    /* istanbul ignore if */
    //   代码覆盖测试
    if (__DEV__ && config.performance && mark) 
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue $vm._name init`, startTag, endTag)
    

    // 组件如果设置了el则挂载到指定的el上
    if (vm.$options.el) 
      vm.$mount(vm.$options.el)
    
  


/**
 * 初始化内部组件
 *
 * @param vm
 * @param options
 */
export function initInternalComponent(
  vm: Component,
  options: InternalComponentOptions
) 
  const opts = (vm.$options = Object.create((vm.constructor as any).options))
  // doing this because it\'s faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions!
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) 
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  


/**
 * 解析构造函数的选项
 *
 * @param Ctor
 * @returns
 */
export function resolveConstructorOptions(Ctor: typeof Component) 
  let options = Ctor.options
  if (Ctor.super) 
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    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) 
        extend(Ctor.extendOptions, modifiedOptions)
      
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) 
        options.components[options.name] = Ctor
      
    
  
  return options


/**
 * 解析修改的选项
 *
 * @param Ctor
 * @returns
 */
function resolveModifiedOptions(
  Ctor: typeof Component
): Record<string, any> | null 
  let modified
  const latest = Ctor.options
  const sealed = Ctor.sealedOptions
  for (const key in latest) 
    if (latest[key] !== sealed[key]) 
      if (!modified) modified = 
      modified[key] = latest[key]
    
  
  return modified


Demo 演示

demo 位于example/docs/01.lifecycle.html

通过debugger的方式,能够更直观的查看到整个调用的过程;这里罗列了选项式 api 和组合式 api,后续的 demo 都会以组合式 api 为主。

具体的 debugger 方法可以查看微软的文档devtools-guide-chromium,一般来说 F9 进行调试即可;如果你想跳过某一函数,那就 F10;

<script src="../../dist/vue.js"></script>

<div id="app">msg</div>

<script>
  debugger

  //   Options API || 设置了el
  //   var app = new Vue(
  //     el: \'#app\',
  //     data: 
  //       msg: \'Hello Vue!\'
  //     ,
  //     beforeCreate() 
  //       console.log(\'beforeCreate\')
  //     ,
  //     created() 
  //       console.log(\'created\')
  //     ,
  //     beforeMount() 
  //       console.log(\'beforeMount\')
  //     ,
  //     mounted() 
  //       console.log(\'mounted\')
  //     
  //   )

  // Options API || 手动$mount
  //   new Vue(
  //     data: () => (
  //       msg: \'helloWord\'
  //     ),
  //     beforeCreate: () => 
  //       console.log(\'beforeCreate\')
  //     ,
  //     created: () => 
  //       console.log(\'created\')
  //     ,
  //     beforeMount: () => 
  //       console.log(\'beforeMount\')
  //     ,
  //     mounted: () => 
  //       console.log(\'mounted\')
  //     
  //   ).$mount(\'#app\')

  // Composition API
  const  ref, beforeCreate, created, beforeMount, mounted  = Vue
  new Vue(
    setup(props) 
      const msg = ref(\'Hello World!\')
      return  msg 
    ,
    beforeCreate() 
      console.log(\'beforeCreate\')
    ,
    created() 
      console.log(\'created\')
    ,
    beforeMount() 
      console.log(\'beforeMount\')
    ,
    mounted() 
      console.log(\'mounted\')
    
  ).$mount(\'#app\')
</script>

内容总结

这里我们总结下init.ts中大致的内容

  1. 生成 vue 实例 Id;
  2. 标记 vue 实例;
  3. 如果是子组件则传入 vue 实例和选项并初始化组件,否则则进行选项参数合并,将用户传入的选项和构造函数本身的选项进行合并;
  4. 初始化实例生命周期相关属性,如:$parent、$root、$children、$refs 等;
  5. 初始化组件相关的事件监听,父级存在监听事件则挂载到当前实例上;
  6. 初始化渲染,如:$slots、$scopedSlots、$createElement、$attrs、$listeners;
  7. 调用beforeCreate生命周期钩子函数
  8. 初始化注入数据,在 data/props 之前进行 inject,以允许一个祖先组件向其所有子孙后代注入一个依赖(说白了就是有个传家宝,爷爷要想传给孙子,那就要爸爸先 inject,再给儿子)
  9. 初始化状态,如:props、setup、methods、data(|| observe)、computed、watch
  10. 在 data/props 之后进行 provide
  11. 调用created生命周期钩子函数,完成初始化
  12. 如果设置了el则自动挂载到对应的元素上,不然就要自己$mount

以上是关于Vue——Vue初始化的主要内容,如果未能解决你的问题,请参考以下文章

vue源码学习- Vue初始化过程

初始化vue项目

Vue——Vue初始化

vue实现初始化数据

vue el-amap 如何重新初始化聚合

vue 2.0源码学习笔记—new Vue ( Vue 初始化过程 )