【Vue】new Vue()做了什么?

前言

Vue 这个框架,我们使用的很是熟练了,那么我们有人想过new Vue()帮我们做了什么?

js 复制代码
const app = new Vue({
    el:"#app",
    // 对象格式
    data:{
        foo:"foo"
    },
    // 函数格式
    data(){
        return {
             foo:"foo"
        }
    }
})

本文涉及源码版本:2.7.14

一、new Vue() 做了什么?

首先,我们可以先了解一下,new Vue 涉及了以下五个方面,所以我们可以从这五个方面进行分析:

  • init初始化
  • mount挂载
  • compiler编译
  • render渲染
  • patcch补丁

1.1 init初始化

执行 init 操作,包括且不限制 initLifecycle、initState等。

通过入口文件,将配置项传入,调用 _init 方法进行初始化

Vue方法的源码如下:vue-2.7.14\src\core\instance\index.ts

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'
import type { GlobalAPI } from 'types/global-api'

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

//@ts-expect-error Vue has function type
initMixin(Vue)
//@ts-expect-error Vue has function type
stateMixin(Vue)
//@ts-expect-error Vue has function type
eventsMixin(Vue)
//@ts-expect-error Vue has function type
lifecycleMixin(Vue)
//@ts-expect-error Vue has function type
renderMixin(Vue)

export default Vue as unknown as GlobalAPI

通过上面的源码我们可以了解到,new Vue()在不出意外的情况下,执行了方法this._init()

this._init()方法主要步骤:

  1. 合并配置项
  2. 初始化生命周期、初始化事件中心、初始化渲染、调用beforCreate钩子,初始化injectionstate/data/props/computed...provide、调用create钩子;
  3. 最后通过判断有无vm.$options.el进行vm.$mount

this._init()源码:vue-2.7.14\src\core\instance\init.ts

js 复制代码
export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    // a uid
    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)
    }

    // 一个标志,将其标记为 vue 实例,而无需执行实例
    vm._isVue = true
    // 避免实例添加observer
    vm.__v_skip = true
    // 影响范围(作用域)
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = true
    // 合并配置:业务逻辑以及组件的一些特性全都放到了 vm.$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 as any)
    } else { // 顶层的vm
      vm.$options = mergeOptions(
        // 一些内置组件(keep-alive...)和指令(show、model...)
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    // 初始化vm._renderProxy 为后期渲染做准备
    // 开发环境并支持proxy vm._renderProxy = new Proxy(vm)
    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 */)
    // 初始化 injections
    initInjections(vm) // resolve injections before data/props
    // 初始化 state
    initState(vm)
    // 初始化 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存在就进入下一步操作 - 挂载
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

1.2 mount挂载

通过 init 源码我们可以了解到在进行一系列初始化操作后,下一步操作就是挂载

挂载过程中完成了最重要的两件事:

  • 初始化
  • 建立更新机制

1.2.1 生成 render 函数 -- vm.prototype.$mount()

vm.$mount()方法主要作用是若配置项中没有 render 方法则将 template 作为编译函数的参数生成该方法,最后调用 runtime 中的 mount 方法

mount源码:vue-2.7.14\src\platforms\web\runtime-with-compiler.ts

js 复制代码
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  // 1. 不允许挂载在 html 和 body 上
  if (el === document.body || el === document.documentElement) {
    __DEV__ &&
      warn(
        `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
      )
    return this
  }

  // 获得配置项
  const options = this.$options
  // resolve template/el and convert to render function
  // render 不存在
  if (!options.render) {
    let template = options.template
    // 2. 初始化 template
    if (template) {
      if (typeof template === 'string') {
        // 如果模板开头是#,说明是一个ID选择器,通过idToTemplate获取相应的innerHtml
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (__DEV__ && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) { // 如果是node对象,直接获取innerHtml
        template = template.innerHTML
      } else { // 不合法配置项
        if (__DEV__) {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // @ts-expect-error
      template = getOuterHTML(el)
    }
    // 3. 将模板转化为 render 函数
    if (template) {
      /* istanbul ignore if */
      // 性能检测
      if (__DEV__ && config.performance && mark) {
        mark('compile')
      }

      // 3.1 将模板转换为 render 函数,得到 ast 抽象树获得的render函数
      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          outputSourceRange: __DEV__,
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        },
        this
      )
      // 3.2保存渲染函数 with(this){return _c('div',{attrs:{\"id\":\"app\"}},[_v(\"\\n......
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      // 性能检测
      if (__DEV__ && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // 4. 调用$mount方法
  return mount.call(this, el, hydrating)
}

1.2.2 完成挂载------mountComponent

这是runtime时的mount方法。最后直接调用了mountComponent方法。所以直接看mountComponent方法吧。

源码:vue-2.7.14\src\platforms\web\runtime\index.ts

js 复制代码
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

mountComponent方法就是 vue 从 beforeMountmounted 的过程。主要步骤如下:

  1. render 函数是生成 DOM 的关键,所以在一开始会先判断是否存在,如果不存在创建一个空的虚拟DOM
  2. 调用生命周期钩子 beforeMounts
  3. 生成 updateComponent 方法,用于触发页面的更新
  4. 生成 watcherOptions 配置,里面 before 方法会触发 beforeUpdate 钩子,将会在触发 updateComponent 前调用
  5. 生成组件更新的 watcher, 前面两部分是为了这部分做准备。new Watcher() 后触发 updateComponent 方法的调用,生成页面虚拟DOM,将 watcher 加入到影响页面变化 data 的依赖收集器中,这样当 data 发送变化时,就会触发页面更新,最终进行 dom diff,生成真实dom
  6. 调用生命周期钩子 mounted

mountComponent方法源码如下:vue-2.7.14\src\core\instance\lifecycle.ts

js 复制代码
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  vm.$el = el
  // render 不存在 - 警告
  if (!vm.$options.render) {
    // @ts-expect-error invalid type
    vm.$options.render = createEmptyVNode
    if (__DEV__) {
      /* 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 */
  if (__DEV__ && config.performance && mark) {
    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 = () => {
      // vm._render() 得到Vnode
      // vm._update() 更新页面
      vm._update(vm._render(), hydrating)
    }
  }

  // 页面更新前会调用 before 函数,触发钩子函数 beforeUpdate
  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }

  if (__DEV__) {
    watcherOptions.onTrack = e => callHook(vm, 'renderTracked', [e])
    watcherOptions.onTrigger = e => callHook(vm, 'renderTriggered', [e])
  }

  // 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,后面会加入到影响页面变化 data 的 deps
  new Watcher(
    vm,
    updateComponent,
    noop,
    watcherOptions,
    true /* isRenderWatcher */
  )
  hydrating = false

  // flush buffer for flush: "pre" watchers queued in setup()
  const preWatchers = vm._preWatchers
  if (preWatchers) {
    for (let i = 0; i < preWatchers.length; i++) {
      preWatchers[i].run()
    }
  }

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  // 挂载完成,触发狗仔函数 mounted
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

1.2.3 更新机制形成 - new Watcher()

这里主要分析上面 new Watcher() 后,如何触发 updateComponent 方法的调用。因此只涉及部分 Watcher 类的代码。

new Watcher() 构造方法里会初始化一些属性,最重要的是将 this.getter = expOrFn,将 updateComponent 方法赋给了 this.getter。最后初始化 this.value 时,调用了 this.get()

new watcher() 构造方法源码如下:vue-2.7.14\src\core\observer\watcher.ts

js 复制代码
constructor(
    vm: Component | null,
    expOrFn: string | (() => any),
    cb: Function,
    options?: WatcherOptions | null,
    isRenderWatcher?: boolean
) {
    recordEffectScope(
      this,
      // if the active effect scope is manually created (not a component scope),
      // prioritize it
      activeEffectScope && !activeEffectScope._vm
        ? activeEffectScope
        : vm
        ? vm._scope
        : undefined
    )
    // 将 watcher 挂载到 vm._watcher 上
    if ((this.vm = vm) && isRenderWatcher) {
      vm._watcher = this
    }
    // options
    // 根据 options 初始化一些属性
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
      if (__DEV__) {
        this.onTrack = options.onTrack
        this.onTrigger = options.onTrigger
      }
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.post = false
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = __DEV__ ? expOrFn.toString() : ''
    // parse expression for getter
    // render 类型 expOrFn 肯定是一个方法,所以 this.getter 就是页面更新方法了
    if (isFunction(expOrFn)) {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        __DEV__ &&
          warn(
            `Failed watching path: "${expOrFn}" ` +
              'Watcher only accepts simple dot-delimited paths. ' +
              'For full control, use a function instead.',
            vm
          )
      }
    }
    // 因为 render 类型的 watcher lazy 值不会是 true(只有computed才会是),所以接下来会调用 get 方法
    this.value = this.lazy ? undefined : this.get()
}

get 方法中,可以看到这行代码 value = this.getter.call(vm, vm)。就是这里调用了 updateComponent 方法。调用了 updateComponent 方法会触发 vm._update(vm.render(), hydrating)。所以接下来分析 vm._render 方法。

get() 方法如下:vue-2.7.14\src\core\observer\watcher.ts

js 复制代码
/**
 * Evaluate the getter, and re-collect dependencies.
 */
get() {
  // 相当于 Dep.target = this
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    // 调用 this.getter 方法,即调用了 updateComponent 方法
    value = this.getter.call(vm, vm)
  } catch (e: any) {
    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
}

1.3 生成虚拟DOM - vm._render()

1.3.1 用户自定义 render 函数

这种情况是用户自定义 render 函数。上面调用 render 方法处,传了一个 vm.$createElement 函数作为参数,所以 hvm.$createElement

js 复制代码
new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

找到 vm.$createElement 源码发现最终调用的是 createElement 方法。

vm.$createElement 源码:vue-2.7.14\src\core\instance\render.ts

js 复制代码
// 这段代码在initRender中

// 用于用户自己手写的 render 函数
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

1.3.2 编译生成的 render 函数

这段html代码通过编译后生成的 render 函数如下:

js 复制代码
<div id="app">
    {{num1}}
    {{hello}}
    <button @click="changeNum">num1</button>
    <button @click="changeNum2">num2</button>
    <div v-for="(item,idx) of arr" :key="item">
        {{item}}
    </div>
    <comp :msg="msg" @log-msg="logMsg"></comp>
</div>

代码中,使用了 with(this){} 说明 {} 所有引用都指向 this 对象(即vm)。所以代码中的 _c = vm._c,因此最终其实也是调用 createElement 函数生成虚拟DOM

在执行这段代码时,会读取到 data 中的属性,比如 num1、arr等等,读取时会调用这些属性的 getter 方法,在 getter 方法中会判断到 Dep.target 中存在值,会将该值存入依赖收集器,从而 完成依赖收齐。

属性发生改变时,触发 setter 方法,通过 depwatcher 最终会调用 updateComponent 方法进行页面的更新。

vm._c 源码:vue-2.7.14\src\core\instance\render.ts

js 复制代码
// src\core\instance\render.ts
// 这段代码在initRender中

// 用于编译后产生的render函数
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

1.3.3 createElement

createElement源码如下:vue-2.7.14\src\core\vdom\create-element.ts

js 复制代码
/**
 * SIMPLE_NORMALIZE: 浅扁平
 * ALWAYS_NORMALIZE:深扁平
*/
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2

// wrapper function for providing a more flexible interface
// without getting yelled at by flow
export function createElement(
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any, // 使用哪种扁平化方式
  alwaysNormalize: boolean // 当render 函数是手写的时候为true
): VNode | Array<VNode> {
  // 实际上是平判断 data 是否存在,如果不存在参数向前进一个
  if (isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

_createElement方法是创建Vnode的核心:

  • 规范 childrenchildren 按照 normalizationType 类型进行扁平化处理,这个目的是规范 children ,对于同一深度层次的元素,不管是单个元素还是钙元素在数组中,只要是属于同一深度层次,都扁平化一个一维数组中
  • 生成 Vnode :如果是普通标签,直接 new Vnode()。Vue组件则需要通过 createComponent 生成 Vnode

_createElement 源码如下:vue-2.7.14\src\core\vdom\create-element.ts

js 复制代码
/**
 * 生成虚拟DOM
 * @param context: vm
 * @param tag: 标签(div、p、ul)
 * @param data: data标签上的属性
 * @param children: 子节点
 * @param normalizationType
 * @returns Vnode: 虚拟dom
*/
export function _createElement(
  context: Component,
  tag?: string | Component | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  // Vnode data 不能为响应式对象
  // 有 __ob__ 代表这个对象为响应式对象
  if (isDef(data) && isDef((data as any).__ob__)) {
    __DEV__ &&
      warn(
        `Avoid using observed data object as vnode data: ${JSON.stringify(
          data
        )}\n` + 'Always create fresh vnode data objects in each render!',
        context
      )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  // <Component :is=""/>
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive key
  if (__DEV__ && isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {
    warn(
      'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
      context
    )
  }
  // support single function children as default scoped slot
  if (isArray(children) && isFunction(children[0])) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  // 根据 normalizationType 对应不同扁平化处理方式
  if (normalizationType === ALWAYS_NORMALIZE) {
    // 深层扁平化
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    // 浅层扁平化
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  // 标签为字符串类型
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (
        __DEV__ &&
        isDef(data) &&
        isDef(data.nativeOn) &&
        data.tag !== 'component'
      ) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      // 创建普通标签的Vnode
      vnode = new VNode(
        config.parsePlatformTagName(tag),
        data,
        children,
        undefined,
        undefined,
        context
      )
    } else if (
      (!data || !data.pre) &&
      isDef((Ctor = resolveAsset(context.$options, 'components', tag)))
    ) {
      // component
      // 为组件,调用 createComponent 创建 Vnode
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      // 直接创建 Vnode
      vnode = new VNode(tag, data, children, undefined, undefined, context)
    }
  } else {
    // direct component options / constructor
    // tag为组件构造函数或组件选项,创建Vue组件
    vnode = createComponent(tag as any, data, context, children)
  }
  if (isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}

1.3.4 创建Vue组件 - createComponent

createComponent作用是返回 Vue 组件的虚拟DOM。同时在这过程中,会构造子类构造函数(这里会调用 _init 方法完成组件初始化)、安装组件钩子函数

所谓的组件化就是把页面拆分成多个组件,组件吧、内部包含自己的 HTML、CSS、JavaScript,这样可以拼成一个模块,并且组件可以复用、拼接,等同于积木一样,一个大的页面可以由很多小的组件拼接而成,接下来我们就用一个例子来看 vue 的组件内部是如何工作的

js 复制代码
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

const app = new Vue({
  render: (h) => h(App),
}).$mount("#app");

console.log(app);

这段代码是通过 render 函数去渲染的,render 函数调用 createElementcreateElement 根据 tag 的不同调用不同的方法生成 Vnode

在例子中,我们可以看到传入的是一个名为 App 的对象,所以继续执行 createComponent

createComponent在这里主要做了三件事:

  1. 把传入的组件对象构造成 vue 的子类
  2. 安装组件钩子函数
  3. 实例化 Vnode 并返回

createComponent源码如下:vue-2.7.14\src\core\vdom\create-component.ts

js 复制代码
export function createComponent(
  Ctor: typeof Component | Function | ComponentOptions | void,
  data: VNodeData | undefined,
  context: Component,
  children?: Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  // 根实例
  // 在 initGlobalAPI 中定义的 vue.options._base = Vue
  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    // 1.
    // 通过 Vue 的 extend 方法,生成子类构造函数,使得子类也有 Vue 根实例的一些方法
    // 其实就是构造Vue的子类
    // src/core/global-api/extend.js
    Ctor = baseCtor.extend(Ctor as typeof Component)
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (__DEV__) {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component
  // 异步组件
  let asyncFactory
  // @ts-expect-error
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
    }
  }

  data = data || {}

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  // 解析构造函数选项,如果全局混合后应用
  // 创建组件构造函数
  resolveConstructorOptions(Ctor as typeof Component)

  // transform component v-model data into props & events
  if (isDef(data.model)) {
    // @ts-expect-error
    transformModel(Ctor.options, data)
  }

  // extract props
  // @ts-expect-error
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // functional component
  // @ts-expect-error
  // 函数式组件
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(
      Ctor as typeof Component,
      propsData,
      data,
      context,
      children
    )
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn

  // @ts-expect-error
  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot

    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // install component management hooks onto the placeholder node
  // 2. 安装组件钩子函数
  installComponentHooks(data)

  // return a placeholder vnode
  // @ts-expect-error
  // 3.实例化 Vnode 并返回。需要注意组件Vnode没有children,这点在之后的patch在分析
  const name = getComponentName(Ctor.options) || tag
  const vnode = new VNode(
    // @ts-expect-error
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data,
    undefined,
    undefined,
    undefined,
    context,
    // @ts-expect-error
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )

  return vnode
}

1.4 生成真实DOM - vm._update(vnode)

_update 源码:vue-2.7.14\src\core\instance\lifecycle.ts

js 复制代码
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm: Component = this
  // 存储在前面的el
  const prevEl = vm.$el
  // 存储在前面的Vnode
  const prevVnode = vm._vnode
  // 存储活动的实例
  const restoreActiveInstance = setActiveInstance(vm)
  vm._vnode = vnode
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) {
    // initial render 首次渲染
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  } else {
    // updates 更新
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  restoreActiveInstance()
  // update __vue__ reference
  // 更新指向
  if (prevEl) {
    prevEl.__vue__ = null
  }
  if (vm.$el) {
    vm.$el.__vue__ = vm
  }
  // if parent is an HOC, update its $el as well
  let wrapper: Component | undefined = vm
  while (
    wrapper &&
    wrapper.$vnode &&
    wrapper.$parent &&
    wrapper.$vnode === wrapper.$parent._vnode
  ) {
    wrapper.$parent.$el = wrapper.$el
    wrapper = wrapper.$parent
  }
  // updated hook is called by the scheduler to ensure that children are
  // updated in a parent's updated hook.
}

二、总结

综上所述,new Vue() 的整体过程大致可以划分为四部分,分别是初始化、挂载、生成虚拟节点,生成真实节点,最后调用 mounted 钩子

  1. 初始化 :合并配置项,初始化生命周期、事件、datacomputedwatch等等,完成了 beforeCreatecreated 的整个过程
  2. 挂载 :如果 options.render 不存在,将 template 编译成 render 函数。接下来就是建立更新机制,创建 watcher,通过 watcher 中的 get 方法,调用组件更新 updateComponent 方法
  3. 生成虚拟DOM :调用更新函数,会调用 vm._render 方法,这个方法会调用 vm.$options.render 方法用于生成虚拟DOM
  4. 生成真实DOM :通过 vm._render 方法得到虚拟DOM后,会作为 vm._update() 方法的参数去生成 Vnode 相应的真实DOM

资料来源

相关推荐
Mr_Xuhhh1 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋2 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿2 小时前
【前端】CSS
前端·css
ggdpzhk2 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•4 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜6 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点6 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow6 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o6 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic7 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端