首页 > 编程 > JavaScript > 正文

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

2019-11-19 12:14:59
字体:
来源:转载
供稿:网友

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表