Vue实例挂载的过程发生了什么

简介: Vue实例挂载的过程发生了什么

一、分析

首先找到vue构造函数

源码位置:src\core\instance\index.js


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

 

options是用户传递过来的配置项,如data、methods等常用的方法

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方法

源码位置:src\core\instance\init.js


 Vue.prototype._init = function (options?: Object) {
 const vm: Component = this
 // a 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
 // 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法
 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 { // 合并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)
     }
   }

 

阅读上面的代码,得到以下结论:

  • 在调用beforeCreate之前,数据初始化并未完成,像dataprops这些属性无法访问到
  • 到了created的时候,数据已经初始化完成,能够访问dataprops这些属性,但这时候并未完成dom的挂载,因此无法访问到dom元素
  • 挂载方法是调用vm.$mount方法

initState方法是完成props/data/method/watch/methods的初始化

源码位置:src\core\instance\state.js


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


这里主要看初始化data的方法为initData,它与initState在同一文件上


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


阅读上面的代码,可以得到以下结论:

  • 初始化顺序:propsmethodsdata
  • data定义的时候可选择函数形式或者对象形式(组件只能为函数形式)

挂载方法是调用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)
 }


阅读上面代码,能得到以下结论:

  • 不要将根元素放到body或者html
  • 可以在对象中定义template/render或者直接使用templateel表示元素选择器
  • 最终都会解析成render函数,调用compileToFunctions,会将template解析成render函数

template的解析步骤大致分为以下几步:

  • html文档片段解析成ast描述符
  • ast描述符解析成字符串
  • 生成render函数

生成render函数,挂载到vm上后,会再次调用mount方法

源码位置:src\platforms\web\runtime\index.js


 // public mount method
 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)
     }
   }
 // 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
 // 监听当前组件状态,当有数据变化时,更新组件
 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
 }


阅读上面代码,得到以下结论:

  • 会触发beforeCreate钩子
  • 定义updateComponent渲染页面视图的方法
  • 监听组件数据,一旦发生变化,触发beforeUpdate生命钩子

updateComponent方法主要执行在vue初始化时声明的renderupdate方法

render的作用主要是生成vnode

源码位置:src\core\instance\render.js


// 定义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
         )
     }
 // set parent vnode. this allows render functions to have access
 // to the data on the placeholder node.
     vm.$vnode = _parentVnode
 // render self
 let vnode
 try {
 // There's no need to maintain a stack because all render fns are called
 // separately from one another. Nested component's render fns are called
 // when parent component is patched.
         currentRenderingInstance = vm
 // 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNode
         vnode = render.call(vm._renderProxy, vm.$createElement)
     } catch (e) {
 handleError(e, vm, `render`)
 // return error render result,
 // or previous vnode to prevent render error causing blank component
 /* istanbul ignore else */
 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,并且更新到页面中

源码位置:src\core\instance\lifecycle.js


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


二、结论

  • new Vue的时候调用会调用_init方法
  • 定义 $set$get$delete$watch 等方法
  • 定义 $on$off$emit$off等事件
  • 定义 _update$forceUpdate$destroy生命周期
  • 调用$mount进行页面的挂载
  • 挂载的时候主要是通过mountComponent方法
  • 定义updateComponent更新函数
  • 执行render生成虚拟DOM
  • _update将虚拟DOM生成真实DOM结构,并且渲染到页面中
相关文章
|
16天前
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
108 17
|
19天前
|
监控 JavaScript 前端开发
Vue 文件批量下载组件封装完整使用方法及优化方案解析
本文详细介绍了批量下载功能的技术实现与组件封装方案。主要包括两种实现方式:**前端打包方案(基于file-saver和jszip)** 和 **后端打包方案**。前者通过前端直接将文件打包为ZIP下载,适合小文件场景;后者由后端生成ZIP文件流返回,适用于大文件或大量文件下载。同时,提供了可复用的Vue组件`BatchDownload`,支持进度条、失败提示等功能。此外,还扩展了下载进度监控和断点续传等高级功能,并针对跨域、性能优化及用户体验改进提出了建议。可根据实际需求选择合适方案并快速集成到项目中。
111 17
|
14天前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
50 1
|
20天前
|
JavaScript 前端开发 UED
Vue 表情包输入组件的实现代码:支持自定义表情库、快捷键发送和输入框联动的聊天表情解决方案
本文详细介绍了在 Vue 项目中实现一个功能完善、交互友好的表情包输入组件的方法,并提供了具体的应用实例。组件设计包含表情分类展示、响应式布局、与输入框的交互及样式定制等功能。通过核心技术实现,如将表情插入输入框光标位置和点击外部关闭选择器,确保用户体验流畅。同时探讨了性能优化策略,如懒加载和虚拟滚动,以及扩展性方案,如自定义主题和国际化支持。最终,展示了如何在聊天界面中集成该组件,为用户提供丰富的表情输入体验。
63 8
|
16天前
|
JavaScript API 开发者
Vue框架中常见指令的应用概述。
通过以上的详细解析,你应该已经初窥Vue.js的指令的威力了。它们是Vue声明式编程模型的核心之一,无论是构建简单的静态网站还是复杂的单页面应用,你都会经常用到。记住,尽管Vue提供了大量预定义的指令,你还可以创建自定义指令以满足特定的需求。为你的Vue应用程序加上这些功能增强器,让编码变得更轻松、更愉快吧!
28 1
|
16天前
|
存储 JavaScript 前端开发
如何高效实现 vue 文件批量下载及相关操作技巧
在Vue项目中,实现文件批量下载是常见需求。例如文档管理系统或图片库应用中,用户可能需要一次性下载多个文件。本文介绍了三种技术方案:1) 使用`file-saver`和`jszip`插件在前端打包文件为ZIP并下载;2) 借助后端接口完成文件压缩与传输;3) 使用`StreamSaver`解决大文件下载问题。同时,通过在线教育平台的实例详细说明了前后端的具体实现步骤,帮助开发者根据项目需求选择合适方案。
50 0
|
16天前
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
30 0
|
JavaScript 测试技术 容器
Vue2+VueRouter2+webpack 构建项目
1). 安装Node环境和npm包管理工具 检测版本 node -v npm -v 图1.png 2). 安装vue-cli(vue脚手架) npm install -g vue-cli --registry=https://registry.
1137 0
|
2月前
|
JavaScript
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
269 4
|
21天前
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
106 21