详解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 相关文章推荐
web网页按比例显示图片实现原理及js代码
Aug 09 Javascript
JavaScript AJAX之惰性载入函数
Aug 27 Javascript
jQuery Ajax()方法使用指南
Nov 19 Javascript
jQuery实现立体式数字动态增加(animate方法)
Dec 21 Javascript
Vue 单文件中的数据传递示例
Mar 21 Javascript
微信小程序中post方法与get方法的封装
Sep 26 Javascript
vue使用监听实现全选反选功能
Jul 06 Javascript
浅谈angular2子组件的事件传递(任意组件事件传递)
Sep 30 Javascript
JS基于Location实现访问Url、重定向及刷新页面的方法分析
Dec 03 Javascript
如何通过shell脚本自动生成vue文件详解
Sep 10 Javascript
vue+vant使用图片预览功能ImagePreview的问题解决
Apr 10 Javascript
多页vue应用的单页面打包方法(内含打包模式的应用)
Jun 11 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
PHP中通过fopen()函数访问远程文件示例
2014/11/18 PHP
PHP校验15位和18位身份证号的类封装
2018/11/07 PHP
ThinkPHP 3.2.3实现加减乘除图片验证码
2018/12/05 PHP
在phpstudy集成环境下的nginx服务器下配置url重写
2019/12/02 PHP
Jquery中对数组的操作代码
2011/08/12 Javascript
javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法详解)
2013/12/16 Javascript
asp.net中oracle 存储过程(图文)
2015/08/12 Javascript
谈谈对offsetleft兼容性的理解
2015/11/11 Javascript
PHP捕捉异常中断的方法
2016/10/24 Javascript
详解VueJs异步动态加载块
2017/03/09 Javascript
vue中axios处理http发送请求的示例(Post和get)
2017/10/13 Javascript
详解NODEJS的http实现
2018/01/04 NodeJs
详解如何在项目中使用jest测试react native组件
2018/02/09 Javascript
mpvue全局引入sass文件的方法步骤
2019/03/06 Javascript
页面内锚点定位及跳转方法总结(推荐)
2019/04/24 Javascript
js 根据对象数组中的属性进行排序实现代码
2019/09/12 Javascript
浅析微信小程序modal弹窗关闭默认会执行cancel问题
2019/10/14 Javascript
vue.js自定义组件实现v-model双向数据绑定的示例代码
2020/01/08 Javascript
javascript浅层克隆、深度克隆对比及实例解析
2020/02/09 Javascript
[01:20:05]DOTA2-DPC中国联赛 正赛 Ehome vs VG BO3 第二场 2月5日
2021/03/11 DOTA
在python3环境下的Django中使用MySQL数据库的实例
2017/08/29 Python
删除DataFrame中值全为NaN或者包含有NaN的列或行方法
2018/11/06 Python
解决pycharm回车之后不能换行或不能缩进的问题
2019/01/16 Python
基于Pycharm加载多个项目过程图解
2020/01/19 Python
pytorch:model.train和model.eval用法及区别详解
2020/02/20 Python
将 Ubuntu 16 和 18 上的 python 升级到最新 python3.8 的方法教程
2020/03/11 Python
Python中有几个关键字
2020/06/04 Python
解决导入django_filters不成功问题No module named 'django_filter'
2020/07/15 Python
如何基于python把文字图片写入word文档
2020/07/31 Python
Python eval函数介绍及用法
2020/11/09 Python
CSS3线性渐变简单实现以及该属性在浏览器中的不同
2012/12/12 HTML / CSS
Aveda美国官网:天然护发产品、洗发水、护发素和沙龙
2016/12/09 全球购物
电子商务专业毕业生求职信
2014/06/12 职场文书
部门活动策划方案
2014/08/16 职场文书
四查四看自我剖析材料
2014/09/19 职场文书
单位工资证明范本
2015/06/12 职场文书