一文了解Vue实例挂载的过程

目录
  • newVue()这个过程中究竟做了些什么?
  • 初始化数据initState(vm)
  • 看下initData
  • 再看下挂载方法是调用vm.$mount
  • render的作用主要是生成vnode
  • 总结

new Vue()这个过程中究竟做了些什么?

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

vue构建函数调用_init方法,但发现本文件中并没有此方法,但仔细可以看到文件下方定义了很多初始化方法

initMixin(Vue);     // 定义 _init
stateMixin(Vue);    // 定义 $set $get $delete $watch 等
eventsMixin(Vue);   // 定义事件  $on  $once $off $emit
lifecycleMixin(Vue);// 定义 _update  $forceUpdate  $destroy
renderMixin(Vue);   // 定义 _render 返回虚拟dom

首先可以看initMixin方法,发现该方法在Vue原型上定义了_init方法

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    vm._uid = uid++
    let startTag, endTag
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
    vm._isVue = true
    // 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else { // 合并vue属性
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      // 初始化proxy拦截器
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化组件生命周期标志位
    initLifecycle(vm)
    // 初始化组件事件侦听
    initEvents(vm)
    // 初始化渲染方法
    initRender(vm)
    callHook(vm, 'beforeCreate')
    // 初始化依赖注入内容,在初始化data、props之前
    initInjections(vm) // resolve injections before data/props
    // 初始化props/data/method/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)
    }
    // 挂载元素
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }

在 init() 中 会通过 callHook(vm,beforeCreate) 来执行 beforeCreate 生命周期函数然后通过initState(vm) 初始化 props 、 methods 、data 接着会通过callHook(vm, 'created') 来执行 created 生命周期函数 最后通过 vm.$mount(vm.$options.el) 来挂载元素

所以:
1. 在beforeCreate 生命周期函数中是无法访问 props和data 因为他们还没有被初始化
2. 同理在created函数中可以访问props,methods、data数据同时也是最早可以调用接口的生
   命周期函数,但是此时dom并未挂载 所以无法访问dom元素
3. 在mounted中 此时dom已经挂载成功 所以可以访问dom元素也是最早可以操作dom元素的生命周期函数

初始化数据 initState(vm)

export function initState (vm: Component) {
  // 初始化组件的watcher列表
  vm._watchers = []
  const opts = vm.$options
  // 初始化props
  if (opts.props) initProps(vm, opts.props)
  // 初始化methods方法
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    // 初始化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)
  }
}

在initState中会先通过initProps 、 initMethods 、 initData 先后分别来初始化 相关数据 在这里会 初始化组件的watcher列表

看下 initData

function initData (vm: Component) {
  let data = vm.$options.data
  // 获取到组件上的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
        )
      }
    }
    // 属性名不能与state名称重复
    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)) { // 验证key值的合法性
      // 将_data中的数据挂载到组件vm上,这样就可以通过this.xxx访问到组件上的数据
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 响应式监听data是数据的变化
  observe(data, true /* asRootData */)
}

需要注意的是:初始化data数据的时候 会校验 props中变量名称和data中的不能重复,在这里会通过observe劫持data的所有属性,如果监听到数据变化就通知订阅者watcher来更新数据,以此来实现数据双向绑定 ,这就是vue实现数据响应式的 发布订阅者模式 挖个坑自己实现该模式

再看下挂载方法是调用vm.$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)
}

在调用vm.$mount方法时 会将template 解析为 抽象语法树 (ast tree) 再将抽象语法树 转换成render语法字符串 最终生成render方法 挂载到vm上后,会再次调用mount方法

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  // 渲染组件
  return mountComponent(this, el, hydrating)
}

调用mountComponent渲染组件

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 如果没有获取解析的render函数,则会抛出警告
  // 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 {
        // 没有获取到vue的模板文件
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  // 执行beforeMount钩子
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && 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 = () => {
      // 实际调⽤是在lifeCycleMixin中定义的_update和renderMixin中定义的_render
      vm._update(vm._render(), hydrating)
    }
  }
  // 监听当前组件状态,当有数据变化时,更新组件
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        // 数据更新引发的组件更新
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

此时会触发beforeMount钩子函数 ,定义updateComponent来渲染页面视图的方法,监听组件数据,一旦发生变化,触发beforeUpdate生命钩子 最后执行 callHook(vm, 'mounted') 钩子函数 完成挂载 updateComponent方法主要执行在vue初始化时声明的render,update方法

render的作用主要是生成vnode

// 定义vue 原型上的render方法
Vue.prototype._render = function (): VNode {
    const vm: Component = this
    // render函数来自于组件的option
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
            _parentVnode.data.scopedSlots,
            vm.$slots,
            vm.$scopedSlots
        )
    }
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
        currentRenderingInstance = vm
        // 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNode
        vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
        handleError(e, vm, `render`)
        if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
            try {
                vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
            } catch (e) {
                handleError(e, vm, `renderError`)
                vnode = vm._vnode
            }
        } else {
            vnode = vm._vnode
        }
    } finally {
        currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
        vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
        if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
            warn(
                'Multiple root nodes returned from render function. Render function ' +
                'should return a single root node.',
                vm
            )
        }
        vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
}

_update主要功能是调用patch,将vnode转换为真实DOM,并且更新到页面中

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    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
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

总结

到此这篇关于Vue实例挂载过程的文章就介绍到这了,更多相关Vue实例挂载过程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入解析Vue源码实例挂载与编译流程实现思路详解

    在正文开始之前,先了解vue基于源码构建的两个版本,一个是 runtime only ,另一个是 runtime加compiler 的版本,两个版本的主要区别在于后者的源码包括了一个编译器. 什么是编译器,百度百科上面的解释是 简单讲,编译器就是将"一种语言(通常为高级语言)"翻译为"另一种语言(通常为低级语言)"的程序.一个现代编译器的主要工作流程:源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) →

  • 一文了解Vue实例挂载的过程

    目录 newVue()这个过程中究竟做了些什么? 初始化数据initState(vm) 看下initData 再看下挂载方法是调用vm.$mount render的作用主要是生成vnode 总结 new Vue()这个过程中究竟做了些什么? function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor an

  • 基于Vue实例生命周期(全面解析)

    前面的话 Vue实例在创建时有一系列的初始化步骤,例如建立数据观察,编译模板,创建数据绑定等.在此过程中,我们可以通过一些定义好的生命周期钩子函数来运行业务逻辑.本文将详细介绍Vue实例的生命周期 图示 下图是Vue实例生命周期的图示 解释 接下来,根据提供的生命周期钩子,对Vue实例各个阶段的情况进行详细说明 [beforeCreate] 在实例开始初始化时同步调用.此时数据观测.事件等都尚未初始化 [created] 在实例创建之后调用.此时已完成数据观测.事件方法,但尚未开始DOM编译,即

  • 详解Vue 实例中的生命周期钩子

    Vue 框架的入口就是 Vue 实例,其实就是框架中的 view model ,它包含页面中的业务处理逻辑.数据模型等,它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑. Vue 实例 在文档中经常会使用 vm 这个变量名表示 Vue 实例,在实例化 Vue 时,需要传入一个选项对象,它可以包含数据(data).模板(template).挂载元素(el).方法(methods).生命周期钩子(lifecyclehook)等选项. Vue 实例化的选项 需要注意的

  • Vue实例中生命周期created和mounted的区别详解

    前言 本文主要跟大家介绍了关于Vue实例中生命周期created和mounted区别的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 生命周期先上图 什么是生命周期 Vue实例有一个完整的生命周期,也就是从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.卸载等一系列过程,我们称这是Vue的生命周期.通俗说就是Vue实例从创建到销毁的过程,就是生命周期. 在Vue的整个生命周期中,它提供了一系列的事件,可以让我们在事件触发时注册js方法,可以让我们用自己注

  • 浅析Vue实例以及生命周期

    最简单的Vue 实例 //html <div id="app"> {{message}} </div> //javascript var vm = new Vue({ el: '#app', data: { message: 'Hello Vue!' } }) 由于 Vue 借鉴了 MVVM 的思想,这里的字符串 "Hello Vue!" 就相当于 Model,DOM 就相当于 View,Vue 实例 "vm" 则是起连接

  • 一文了解Vue中的nextTick

    Vue中的 nextTick 涉及到Vue中DOM的异步更新,感觉很有意思,特意了解了一下.其中关于 nextTick 的源码涉及到不少知识,很多不太理解,暂且根据自己的一些感悟介绍下 nextTick . 一.示例 先来一个示例了解下关于Vue中的DOM更新以及 nextTick 的作用. 模板 <div class="app"> <div ref="msgDiv">{{msg}}</div> <div v-if=&quo

  • vue实例的选项总结

    一.数据 API:https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E6%95%B0%E6%8D%AE 1. data 类型:Object | Function 限制:组件的定义只接受 function 详细:Vue 实例的数据对象.Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化.对象必须是纯粹的对象 (含有零个或多个的 key/value 对).当一个组件被定义,data 必须声明

  • vue实例成员 插值表达式 过滤器基础教程示例详解

    目录 一. 什么是Vue 二.为什么学Vue 三.如何使用Vue 下载安装? 插值表达式 四.vue特点 1.虚拟DOM 2.数据的双向绑定 3.单页面应用 4.数据驱动 五.Vue实例 六.实例成员 - 挂载点 | el - 自定义插值表达式括号| delimiters - 数据 | data - 过滤器 | filters - 方法 | methods - js对象(即字典)补充 - 插值表达式转义 | delimters - 计算属性 | computed - 监听属性 | watch 一

  • Vue实例简单方法介绍

    Vue实例的一些简单方法 <!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <script src="http://unpkg.com/vue/dist/vue.js"></script> </head> <body> <div id="box&

随机推荐