详解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 相关文章推荐
JavaScript中的Array对象使用说明
Jan 17 Javascript
IE与FireFox中的childNodes区别
Oct 20 Javascript
用Move.js配合创建CSS3动画的入门指引
Jul 22 Javascript
javascript中new关键字详解
Dec 14 Javascript
基于canvas实现的绚丽圆圈效果完整实例
Jan 26 Javascript
ES6新特性之变量和字符串用法示例
Apr 01 Javascript
angularjs指令之绑定策略(@、=、&amp;)
Apr 13 Javascript
详解React开发中使用require.ensure()按需加载ES6组件
May 12 Javascript
Vue的事件响应式进度条组件实例详解
Feb 04 Javascript
Angular4 Select选择改变事件的方法
Oct 09 Javascript
微信小程序button标签open-type属性原理解析
Jan 21 Javascript
基于Electron实现桌面应用开发代码实例
Jul 07 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中eval函数的危害与正确禁用方法
2014/06/30 PHP
alixixi runcode.asp的代码不错的应用
2007/08/08 Javascript
jQuery 定时局部刷新(setInterval)
2010/11/19 Javascript
基于jquery的合并table相同单元格的插件(精简版)
2011/04/05 Javascript
js函数的延迟加载实现代码
2012/10/11 Javascript
Jquery uploadify图片上传插件无法上传的解决方法
2013/12/16 Javascript
JS二维数组的定义说明
2014/03/03 Javascript
JavaScript极简入门教程(二):对象和函数
2014/10/25 Javascript
Javascript常用字符串判断函数代码分享
2014/12/08 Javascript
jQuery实现带滑动条的菜单效果代码
2015/08/26 Javascript
JQuery解析XML的方法小结
2016/04/02 Javascript
Javascript类型系统之String字符串类型详解
2016/06/21 Javascript
jquery中live()方法和bind()方法区别分析
2016/06/23 Javascript
JQuery实现文字无缝滚动效果示例代码(Marquee插件)
2017/03/07 Javascript
nodejs socket服务端和客户端简单通信功能
2017/09/14 NodeJs
当vue路由变化时,改变导航栏的样式方法
2018/08/22 Javascript
element-ui 关于获取select 的label值方法
2018/08/24 Javascript
vue3.0 CLI - 1 - npm 安装与初始化的入门教程
2018/09/14 Javascript
关于AngularJS中ng-repeat不更新视图的解决方法
2018/09/30 Javascript
python矩阵的转置和逆转实例
2018/12/12 Python
浅谈Python小波分析库Pywavelets的一点使用心得
2019/07/09 Python
学习和使用python的13个理由
2019/07/30 Python
Python unittest单元测试openpyxl实现过程解析
2020/05/27 Python
python中os包的用法
2020/06/01 Python
keras导入weights方式
2020/06/12 Python
Kears 使用:通过回调函数保存最佳准确率下的模型操作
2020/06/17 Python
python 利用matplotlib在3D空间中绘制平面的案例
2021/02/06 Python
Furla官网:意大利著名的皮革品牌
2019/08/06 全球购物
哈萨克斯坦最大的时装、鞋子和配饰在线商店:Lamoda.kz
2019/11/19 全球购物
实习老师离校感言
2014/02/03 职场文书
数控专业毕业生自荐信范文
2014/03/04 职场文书
支部组织生活会方案
2014/06/10 职场文书
群众路线组织生活会发言材料
2014/10/17 职场文书
学生打架检讨书
2014/10/20 职场文书
精神文明建设先进个人事迹材料
2014/12/24 职场文书
婚礼答谢礼品
2015/01/20 职场文书