Vue实例挂载的过程

586 阅读2分钟

我们今天来说一下new Vue()的这个过程做了些什么?

首先我们找到Vue这个构造函数,如下所示,可以发现options是用户传过来的配置,比如methods,data等等

image.png Vue函数调用了_init()方法,_init方法来自'./init' ,initMixin(Vue)

image.png

image.png

可以发现initLifeCycle是用来初始化Vue实例的组件生命周期标志位

init函数中的一些方法的作用
initLifeCycle(vm)初始化Vue实例的组件生命周期标志位
initEvents(vm)初始化组件事件监听
initRender(vm)初始化渲染方法
callHook(vm,'beforeCreate')
initInjections(vm)初始化依赖注入内容,在初始化data,props之前
initState(vm)初始化props、data、methods、watch、computed
callHook(vm,'created')

通过上面的代码可以知道,在调用beforeCreate之前,数据还没有初始化完成,data,props,这些属性是访问不到的。 等到了created的时候,数据已经初始化完成,所以这个时候能够访问data、props属性,但是这个时候只是完成了数据的初始化,并没有完成dom的挂载,所以也是无法访问到dom元素的。

这里的initstate方法主要是用来初始化数据,

image.png 下面的initData主要是用来初始化data的,可以发现data在定义的时候既可以是对象也可以是函数,然后数据的初始化顺序就是上面initState里面的执行顺序,也就是props、methods、data、computed、watch image.png

下面的是$mount方法,


Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // 获取或查询元素
  el = el && query(el)

  /* istanbul ignore if */
  // vue 不允许直接挂载到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
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    // 存在template模板,解析vue模板文件
    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) {
      // 通过选择器获取元素内容
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      /**
       *  1.将temmplate解析ast tree
       *  2.将ast tree转换成render语法字符串
       *  3.生成render方法
       */
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      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)
}

通过上面的代码可以看出来,

(1)根元素是不可以放在body或者html上的

(2)可以在对象中定义template/render或者直接使用template,el表示元素选择器

(3)最终都会解析成render函数,调用解析的函数,然后template会被解析成render函数

对template解析步骤大概分为以下几步:

(1)将html解析成抽象语法树

(2)将抽象语法树解析成字符串

(3) 生成render函数

在代码里可以看到,生成的render函数最终又会被添加到options配置项上面,然后options会被挂载到Vue实例上,然后会再次调用mount方法。如下图的代码所示

image.png 调用mountComponent方法来进行渲染组件,下面是对mountComponent方法的声明 image.png 由上图的代码可以看出来,如过没有render函数的话,也就是没有解析模板文件生成render函数,那么会报一个警告, 接着往下看代码

image.png 接着会执行beforeMount钩子函数,下面定义updateComponent方法来渲染页面视图,继续往下看,监听组件数据,一旦发生改变,触发beforeUpdate生命周期函数。所以我们看完了他的执行方法,得出结论,updateComponent方法主要执行在vue初始化时声明的render,update方法。由代码可以看出,render函数主要是生成vnode

接下来看render和update函数 直接上代码

image.png

可以看出render函数是Vue实例的options选项里面的。

_update函数主要是调用patch函数,将vnode转化为真实的dom,并且更新到页面中 image.png

总结:

new Vue的时候先_init方法

在_init方法里面

(1)定义 $watch等等;

(2)定义 $emit等等事件

(3)定义_update,$destory生命周期

·调用$mount进行页面的挂载

·挂载的时候主要是通过mountComponent方法

·定义updateComponent更新函数

·执行render函数生成虚拟dom

·_update将vnode生成真实的dom结构,并且渲染到页面上