vue2和vue3源码初始化

170 阅读3分钟

vue2初始化流程图 vue2初始化.png

vue3初始化流程图 vue3初始化.png

Vue2初始化过程详解

Vue的初始化总共分成两个步骤

  1. new Vue(options)过程
  2. $mount()过程

new Vue(options)过程

1. src/core/instance/index.js

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'

// Vue的构造函数
// new Vue(options)执行的地方
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')
  }
  // 初始化
  // 这个init哪里来的呢? initMixin全局混入来的
  this._init(options)
}

// 下面的几个函数用户扩展一些Vue实例方法和属性
initMixin(Vue)
// $set $delete $watch
stateMixin(Vue)
// $on $once $emit $off
eventsMixin(Vue)
// $forceupdate $destory
lifecycleMixin(Vue)
// nextTick
renderMixin(Vue)

export default Vue

代码描述:在new Vue(options)时候内部除了做了一些错误处理,核心代码之执行了一个_init()方法,_init()方法是通过下面的initMixin全局混入来的。下面的一些混入的含义是
- initMixin 混入了——init函数
- stateMixin 与组件状态相关的混入,如$set $delete $watch
- eventsMixin与事件相关的混入  $on $once $emit $off
- liftcycleMixin $forceUpdate $destory
- renderMixin nextTick

2. src/core/instance/index.js

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    // 定义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)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    // 选择合并
    //在刚刚我们调试$mount的时候你会发现里面的选项超过了我们配置的选项,这些选项是怎么来的呢??
    // 就是在这里进行的选项合并
    // 合并的是系统的选项和用户的选项
    // 之后你就可以通过this.$options.xxx来进行访问
    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 {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    // 这里就是初始化过程
    // 经典面试题 new Vue()都做了什么??
    // 就是把下面描述一遍就行
    vm._self = vm
    // 初始化生命周期(就是实例属性的初始化)
    // $patent $children $root $refs 实例属性
    initLifecycle(vm)
    // 初始化自定义事件
    // <my-com @my-click="xxx"></my-com>
    initEvents(vm)
    // 初始化渲染器
    // $slots $slopeSlot _c $createElement
    // _c --> 和$createElement基本是一样的, 用于编译器生成的渲染函数(标准函数)
    // $createElement 就是render中的h
    // _c $createElement 有一层柯理化在里面
    initRender(vm)
    callHook(vm, 'beforeCreate')
    // 接下来都是组件状态的处理
    // project / inject 跨层级穿惨 相当于react 的 context
    // 首先注入祖辈传递下来的数据
    initInjections(vm) // resolve injections before data/props
    // data computed props watch methods
    // 就是初始化需要响应式处理
    initState(vm)
    // 传递给后代
    initProvide(vm) // resolve provide after data/props
    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)
    }
    // 如果有el选项,就自动挂载
    // 这就是为什么在new Vue中传入el不需要手动$mount的原因
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

代码描述:这块是new Vue(options)的核心内容,做了以下几件事情
- vm._uid = uid++
- mergeOptions:将用户传递进来的options和系统默认自带的options做一个合并,系统会默认增加一些公用的options,指令的show,model; 全局组件的transform transform-group nextTick,并将合并后的值赋值给vm.$options(后续就可通过this.$options来访问这些options)
- 最重要的内容,做了一些初始化的工作,见上面代码的注释
- 开始挂载

$mount()过程

1. src/platforms/web/entry-runtime-with-compiler

/**
 * const mount = Vue.prototype.$mount
 * Vue.prototype.$mount = xxxx
 * 只要看到这种写法你就应该知道是在做扩展
 * 为啥要在这里重新定义一遍mount呢?
 * 因为runtime-only版本不用处理这些东西,只需要runtine/index的mount方法即可
 * 但是runtine-with-comiiler这个带编译器的版本需要处理以下内容
 */
// 扩展$mount方法
const mount = Vue.prototype.$mount
// Vue.$mount('#app')
Vue.prototype.$mount = function (
  // 宿主
  el?: string | Element,
  // 注水, 这个参数仅用于服务端渲染
  hydrating?: boolean
): Component {
  // 查询这个选择器获得真实元素
  el = el && query(el)

  /* istanbul ignore if */
  // el不可以是body或者是html,因为会覆盖的,如果是html或者body,会报警告
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
  // 获取组件选项
  const options = this.$options
  /**
   * 这里有个小问题??
   * el, render, template这三个的关系是怎样的
   * render > template > el
   */
  // resolve template/el and convert to render function
  // 看看是否存在render选项
  // 有render, template el啥的都不执行了
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // 最后才判断el选项
      template = getOuterHTML(el)
    }

    // 千辛万苦拿到template,是为了编译模版为render函数
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      // 执行编译, 获取render
      // 这块就是你心心念念的编译环节
      // 之后有专门的编译环节来讲
      // * 重点
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      // 重新赋值给选项的render,以备将来使用
      // 最终vue只认render函数
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      // 这是控制台输入,我们不关心
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // 执行默认挂载行为
  return mount.call(this, el, hydrating)
}
代码描述:是对mount()方法的扩展,因为runtime-only版本不用处理这些东西,只需要runtine/index的mount方法即可,但是runtine-with-comiiler这个带编译器的版本需要处理以下内容,内部就是想要拿到render函数,没有render通过编译(后面会说)拿到render,最后执行mount()

2. src/platforms/web/runtime/index.js

// public mount method
// *实现了挂载方法
// 注意: 实现$mount方法是在这里, entry-runtime-with-compiler.js是扩展了这个方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  // 重点内容 mountComponent
  return mountComponent(this, el, hydrating)
}

代码描述:就是执行了mountComponent

3. src/core/instance/liftcycle.js

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 没有render就做一个错误处理
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  // 生命周期beforeMount
  callHook(vm, 'beforeMount')

  // 定义一个重点函数 updateComponent
  let updateComponent
  /* istanbul ignore if */
  // mark:性能埋点
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    // vue提供的性能埋点
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    // updateComponent
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  // 调用watcher,在内部去调用updateComponent
  // 这是个渲染watcher
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

3.src/core/observe/watcher.js

constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean // 是否是渲染watcher的标记位
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    // 之后会有用到
    if (typeof expOrFn === 'function') {
      // 看清楚这行代码
      this.getter = expOrFn
    } else {
      // 也需要看下
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    // 执行 this.get()
    this.value = this.lazy
      ? undefined
      : this.get()
  }
  
   get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 执行this.getter 相当于执行了传递进来的updateComponent
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }