详解vue挂载到dom上会发生什么


Posted in Javascript onJanuary 20, 2019

vue 挂载到dom 元素后发生了什么

前一篇文章分析了new vue() 初始化时所执行的操作,主要包括调用vue._init 执行一系列的初始化,包括生命周期,事件系统,beforeCreate和Created hook,在在这里发生,重点分析了 initState,即对我们常用到的data props computed 等等进行的初始化,最后,执行$mount 对dom进行了挂载,本篇文章将对挂载后所发生的事情进行进一步阐述,

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

mount 的代码很简单,直接执行了moutComponent方法,

export function mountComponent (
 vm: Component,
 el: ?Element,
 hydrating?: boolean
): Component {
 vm.$el = el
 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
  )
  }
 }
 }
 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 = () => {
  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
}

moutComponent 这里判断了render函数,正常开发过程中,对于dom的写法有很多种,可以直接写templete,也可以写render函数,也可以直接把dom写在挂载元素里面,但是在编译阶段(通常是通过webpack执行的),统统会把这些写法都编译成render函数,所以,最后执行的都是render函数,判断完render可以看到,beforeMount hook在这里执行,最后执行了new Watcher() 我们进入new Watcher

export default class Watcher {
 vm: Component;
 expression: string;
 cb: Function;
 id: number;
 deep: boolean;
 user: boolean;
 lazy: boolean;
 sync: boolean;
 dirty: boolean;
 active: boolean;
 deps: Array<Dep>;
 newDeps: Array<Dep>;
 depIds: SimpleSet;
 newDepIds: SimpleSet;
 before: ?Function;
 getter: Function;
 value: any;

 constructor (
 vm: Component,
 expOrFn: string | Function,
 cb: Function,
 options?: ?Object,
 isRenderWatcher?: boolean
 ) {
 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.value = this.lazy
  ? undefined
  : this.get()
 }

其他方法暂时不提,可以看到,但是我们也能大致猜到他在做些什么,这里只是截取了部分Watcher的构造方法,,重点是最后执行了this.get 而this.get则执行了this.getter,最后等于执行了Watcher构造方法中传入的第二个参数,也就是上一环节moutComponent中的updateComponent方法,updateComponent方法也是在moutComponent方法中定义

updateComponent = () => {
 vm._update(vm._render(), hydrating)
 }

这里先是执行编译而成的render方法,然后作为参数传到_update方法中执行,render方法执行后返回一个vnode 即Virtual dom,然后将这个Virtual dom作为参数传到update方法中,这里我们先介绍一下Virtual dom 然后在介绍最后执行挂载的update方法,

render函数

Vue.prototype._render = function (): VNode {
 const vm: Component = this
 const { render, _parentVnode } = vm.$options

 if (_parentVnode) {
  vm.$scopedSlots = normalizeScopedSlots(
  _parentVnode.data.scopedSlots,
  vm.$slots
  )
 }

 // 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 {
  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
  }
 }
 // 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
 }

根据flow 的类型定义,我们可以看到,_render函数最后返回一个vnode,_render主要代码 在第一个try catch中,vnode = render.call(vm._renderProxy,vm.$CREATRElement) ,第一个参数为当前上下文this 其实就是vm本身,第二个参数是实际执行的方法,当我们在手写render函数时,比如这样

render:h=>{
  return h(
    "div",
    123
   )
  }

这时候我们使用的h 就是传入的$createElement方法,然后我们来看一下createElement方法,在看creatElement之前,我们先简单介绍一下vdom,因为createElement返回的就是一个vdom,vdom其实就是真实dom对象的一个映射,主要包含标签名字tag 和在它下面的标签 children 还有一些属性的定义等等,当我们在进行dom改变时首先是数据的改变,数据的改变映射到 vdom中,然后改变vdom,改变vdom是对js数据层面的改变所以说代价很小,在这一过程中我们还可以进行针对性的优化,复用等,最后把优化后的改变部分通过dom操作操作到真实的dom上去,另外,通过vdom这层的定义我们不仅仅可以把vdom映射到web文档流上,甚至可以映射到app端的文档流,桌面应用的文档流多种,这里引用一下vue js作者对vdom的评价:Virtual DOM真正价值从来不是性能,而是它 1: 为函数式的ui编程方式打开了大门,2 :可以渲染到dom以外的backend 比如 ReactNative 。

下面我们来继续介绍creatElement

export function _createElement (
 context: Component,
 tag?: string | Class<Component> | Function | Object,
 data?: VNodeData,
 children?: any,
 normalizationType?: number
): VNode | Array<VNode> {
 if (isDef(data) && isDef((data: any).__ob__)) {
 process.env.NODE_ENV !== 'production' && 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
 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 (process.env.NODE_ENV !== 'production' &&
 isDef(data) && isDef(data.key) && !isPrimitive(data.key)
 ) {
 if (!__WEEX__ || !('@binding' in 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 (Array.isArray(children) &&
 typeof children[0] === 'function'
 ) {
 data = data || {}
 data.scopedSlots = { default: children[0] }
 children.length = 0
 }
 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
  vnode = new VNode(
  config.parsePlatformTagName(tag), data, children,
  undefined, undefined, context
  )
 } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
  // component
  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 = new VNode(
  tag, data, children,
  undefined, undefined, context
  )
 }
 } else {
 // direct component options / constructor
 vnode = createComponent(tag, data, context, children)
 }
 if (Array.isArray(vnode)) {
 return vnode
 } else if (isDef(vnode)) {
 if (isDef(ns)) applyNS(vnode, ns)
 if (isDef(data)) registerDeepBindings(data)
 return vnode
 } else {
 return createEmptyVNode()
 }
}

creatElement 最后结果时返回一个new VNode,并将craete时传入的参数,经过处理,传到VNode的初始化中,这里有几种情况,createEmptyVNode,没有传参数,或参数错误,会返回一个空的vnode,如果tag 时浏览器的标签如div h3 p等,会返回一个保留VNode,等等,最后,回到上面,vnode 创建完毕,_render会返回这个vnode,最后走回vm._update(),update 中,便是将vnode 通过dom操作插入到真正的文档流中,下一节我们聊聊update

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jquery插件之easing 动态菜单
Aug 21 Javascript
Node.js抓取中文网页乱码问题和解决方法
Feb 10 Javascript
JavaScript生成的动态下雨背景效果实现方法
Feb 25 Javascript
javascript实现简单的鼠标拖动效果实例
Apr 10 Javascript
基于javascript实现最简单的选项卡切换效果
May 16 Javascript
Vue.js自定义指令的用法与实例解析
Jan 18 Javascript
用POSTMAN发送JSON格式的POST请求示例
Sep 04 Javascript
浅析vue插槽和作用域插槽的理解
Apr 22 Javascript
axios实现文件上传并获取进度
Mar 25 Javascript
JS相册图片抖动放大展示效果的示例代码
Jan 29 Javascript
原生JS实现音乐播放器的示例代码
Feb 25 Javascript
angular异步验证器防抖实例详解
Mar 31 Javascript
vue-cli3 从搭建到优化的详细步骤
Jan 20 #Javascript
微信小程序实现富文本图片宽度自适应的方法
Jan 20 #Javascript
实例介绍JavaScript中多种组合继承
Jan 20 #Javascript
VUE简单的定时器实时刷新的实现方法
Jan 20 #Javascript
小程序开发中如何使用async-await并封装公共异步请求的方法
Jan 20 #Javascript
Django+vue跨域问题解决的详细步骤
Jan 20 #Javascript
如何在vue里面优雅的解决跨域(路由冲突问题)
Jan 20 #Javascript
You might like
介绍几个array库的新函数 php
2006/12/29 PHP
php文件上传表单摘自drupal的代码
2011/02/15 PHP
laravel框架学习笔记之组件化开发实现方法
2020/02/01 PHP
Javascript中暂停功能的实现代码
2007/03/04 Javascript
JS匀速运动演示示例代码
2013/11/26 Javascript
jQuery处理xml格式的返回数据(实例解析)
2013/11/28 Javascript
使用JavaScript实现ajax的实例代码
2016/05/11 Javascript
JS简单封装的图片无缝滚动效果示例【测试可用】
2017/03/22 Javascript
Ajax验证用户名或昵称是否已被注册
2017/04/05 Javascript
详解关于微信setData回调函数中的坑
2019/02/18 Javascript
ECharts地图绘制和钻取简易接口详解
2019/07/12 Javascript
JavaScript 反射和属性赋值实例解析
2019/10/28 Javascript
vue实现计步器功能
2019/11/01 Javascript
微信小程序swiper组件实现抖音翻页切换视频功能的实例代码
2020/06/24 Javascript
jQuery实现简单全选框
2020/09/13 jQuery
[02:46]2014DOTA2国际邀请赛 选手为你解读比赛MVP充满梦想
2014/07/09 DOTA
[56:00]DOTA2上海特级锦标赛主赛事日 - 4 胜者组决赛Secret VS Liquid第一局
2016/03/05 DOTA
Pycharm无法显示动态图片的解决方法
2018/10/28 Python
Python基础学习之函数方法实例详解
2019/06/18 Python
python函数的作用域及关键字详解
2019/08/20 Python
Python使用itchat模块实现群聊转发,自动回复功能示例
2019/08/26 Python
Python使用GitPython操作Git版本库的方法
2020/02/29 Python
python实现飞船大战
2020/04/24 Python
selenium切换标签页解决get超时问题的完整代码
2020/08/30 Python
html5 div布局与table布局详解
2016/11/16 HTML / CSS
HTML5新标签兼容——&gt; 的两种方法
2018/09/12 HTML / CSS
香港永安旅游网:Wing On Travel
2017/04/10 全球购物
英国最大的网上药品商店:Chemist Direct
2017/12/16 全球购物
Dr. Martens马汀博士德国官网:马丁靴鼻祖
2019/12/26 全球购物
幼儿园春游活动方案
2014/01/19 职场文书
运动会广播稿400字
2014/01/25 职场文书
我的求职择业计划书
2014/04/04 职场文书
入党介绍人评语
2014/05/06 职场文书
社团活动总结书
2014/06/27 职场文书
PyTorch 如何检查模型梯度是否可导
2021/06/05 Python
Vue全局事件总线你了解吗
2022/02/24 Vue.js