详解vue挂载到dom上会发生什么
vue挂载到dom元素后发生了什么
前一篇文章分析了newvue()初始化时所执行的操作,主要包括调用vue._init执行一系列的初始化,包括生命周期,事件系统,beforeCreate和Createdhook,在在这里发生,重点分析了initState,即对我们常用到的datapropscomputed等等进行的初始化,最后,执行$mount对dom进行了挂载,本篇文章将对挂载后所发生的事情进行进一步阐述,
Vue.prototype.$mount=function( el?:string|Element, hydrating?:boolean ):Component{ el=el&&inBrowser?query(el):undefined returnmountComponent(this,el,hydrating) }
mount的代码很简单,直接执行了moutComponent方法,
exportfunctionmountComponent( vm:Component, el:?Element, hydrating?:boolean ):Component{ vm.$el=el if(!vm.$options.render){ vm.$options.render=createEmptyVNode if(process.env.NODE_ENV!=='production'){ /*istanbulignoreif*/ if((vm.$options.template&&vm.$options.template.charAt(0)!=='#')|| vm.$options.el||el){ warn( 'Youareusingtheruntime-onlybuildofVuewherethetemplate'+ 'compilerisnotavailable.Eitherpre-compilethetemplatesinto'+ 'renderfunctions,orusethecompiler-includedbuild.', vm ) }else{ warn( 'Failedtomountcomponent:templateorrenderfunctionnotdefined.', vm ) } } } callHook(vm,'beforeMount') letupdateComponent /*istanbulignoreif*/ if(process.env.NODE_ENV!=='production'&&config.performance&&mark){ updateComponent=()=>{ constname=vm._name constid=vm._uid conststartTag=`vue-perf-start:${id}` constendTag=`vue-perf-end:${id}` mark(startTag) constvnode=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) } } //wesetthistovm._watcherinsidethewatcher'sconstructor //sincethewatcher'sinitialpatchmaycall$forceUpdate(e.g.insidechild //component'smountedhook),whichreliesonvm._watcherbeingalreadydefined newWatcher(vm,updateComponent,noop,{ before(){ if(vm._isMounted&&!vm._isDestroyed){ callHook(vm,'beforeUpdate') } } },true/*isRenderWatcher*/) hydrating=false //manuallymountedinstance,callmountedonself //mountediscalledforrender-createdchildcomponentsinitsinsertedhook if(vm.$vnode==null){ vm._isMounted=true callHook(vm,'mounted') } returnvm }
moutComponent这里判断了render函数,正常开发过程中,对于dom的写法有很多种,可以直接写templete,也可以写render函数,也可以直接把dom写在挂载元素里面,但是在编译阶段(通常是通过webpack执行的),统统会把这些写法都编译成render函数,所以,最后执行的都是render函数,判断完render可以看到,beforeMounthook在这里执行,最后执行了newWatcher()我们进入newWatcher
exportdefaultclassWatcher{ vm:Component; expression:string; cb:Function; id:number; deep:boolean; user:boolean; lazy:boolean; sync:boolean; dirty:boolean; active:boolean; deps:Array; newDeps:Array ; 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//uidforbatching this.active=true this.dirty=this.lazy//forlazywatchers this.deps=[] this.newDeps=[] this.depIds=newSet() this.newDepIds=newSet() this.expression=process.env.NODE_ENV!=='production' ?expOrFn.toString() :'' //parseexpressionforgetter if(typeofexpOrFn==='function'){ this.getter=expOrFn }else{ this.getter=parsePath(expOrFn) if(!this.getter){ this.getter=noop process.env.NODE_ENV!=='production'&&warn( `Failedwatchingpath:"${expOrFn}"`+ 'Watcheronlyacceptssimpledot-delimitedpaths.'+ 'Forfullcontrol,useafunctioninstead.', 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即Virtualdom,然后将这个Virtualdom作为参数传到update方法中,这里我们先介绍一下Virtualdom然后在介绍最后执行挂载的update方法,
render函数
Vue.prototype._render=function():VNode{ constvm:Component=this const{render,_parentVnode}=vm.$options if(_parentVnode){ vm.$scopedSlots=normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots ) } //setparentvnode.thisallowsrenderfunctionstohaveaccess //tothedataontheplaceholdernode. vm.$vnode=_parentVnode //renderself letvnode try{ vnode=render.call(vm._renderProxy,vm.$createElement) }catch(e){ handleError(e,vm,`render`) //returnerrorrenderresult, //orpreviousvnodetopreventrendererrorcausingblankcomponent /*istanbulignoreelse*/ 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 } } //ifthereturnedarraycontainsonlyasinglenode,allowit if(Array.isArray(vnode)&&vnode.length===1){ vnode=vnode[0] } //returnemptyvnodeincasetherenderfunctionerroredout if(!(vnodeinstanceofVNode)){ if(process.env.NODE_ENV!=='production'&&Array.isArray(vnode)){ warn( 'Multiplerootnodesreturnedfromrenderfunction.Renderfunction'+ 'shouldreturnasinglerootnode.', vm ) } vnode=createEmptyVNode() } //setparent vnode.parent=_parentVnode returnvnode }
根据flow的类型定义,我们可以看到,_render函数最后返回一个vnode,_render主要代码在第一个trycatch中,vnode=render.call(vm._renderProxy,vm.$CREATRElement),第一个参数为当前上下文this其实就是vm本身,第二个参数是实际执行的方法,当我们在手写render函数时,比如这样
render:h=>{ returnh( "div", 123 ) }
这时候我们使用的h就是传入的$createElement方法,然后我们来看一下createElement方法,在看creatElement之前,我们先简单介绍一下vdom,因为createElement返回的就是一个vdom,vdom其实就是真实dom对象的一个映射,主要包含标签名字tag和在它下面的标签children还有一些属性的定义等等,当我们在进行dom改变时首先是数据的改变,数据的改变映射到vdom中,然后改变vdom,改变vdom是对js数据层面的改变所以说代价很小,在这一过程中我们还可以进行针对性的优化,复用等,最后把优化后的改变部分通过dom操作操作到真实的dom上去,另外,通过vdom这层的定义我们不仅仅可以把vdom映射到web文档流上,甚至可以映射到app端的文档流,桌面应用的文档流多种,这里引用一下vuejs作者对vdom的评价:VirtualDOM真正价值从来不是性能,而是它1:为函数式的ui编程方式打开了大门,2:可以渲染到dom以外的backend比如ReactNative。
下面我们来继续介绍creatElement
exportfunction_createElement( context:Component, tag?:string|Class|Function|Object, data?:VNodeData, children?:any, normalizationType?:number ):VNode|Array { if(isDef(data)&&isDef((data:any).__ob__)){ process.env.NODE_ENV!=='production'&&warn( `Avoidusingobserveddataobjectasvnodedata:${JSON.stringify(data)}\n`+ 'Alwayscreatefreshvnodedataobjectsineachrender!', context ) returncreateEmptyVNode() } //objectsyntaxinv-bind if(isDef(data)&&isDef(data.is)){ tag=data.is } if(!tag){ //incaseofcomponent:issettofalsyvalue returncreateEmptyVNode() } //warnagainstnon-primitivekey if(process.env.NODE_ENV!=='production'&& isDef(data)&&isDef(data.key)&&!isPrimitive(data.key) ){ if(!__WEEX__||!('@binding'indata.key)){ warn( 'Avoidusingnon-primitivevalueaskey,'+ 'usestring/numbervalueinstead.', context ) } } //supportsinglefunctionchildrenasdefaultscopedslot if(Array.isArray(children)&& typeofchildren[0]==='function' ){ data=data||{} data.scopedSlots={default:children[0]} children.length=0 } if(normalizationType===ALWAYS_NORMALIZE){ children=normalizeChildren(children) }elseif(normalizationType===SIMPLE_NORMALIZE){ children=simpleNormalizeChildren(children) } letvnode,ns if(typeoftag==='string'){ letCtor ns=(context.$vnode&&context.$vnode.ns)||config.getTagNamespace(tag) if(config.isReservedTag(tag)){ //platformbuilt-inelements vnode=newVNode( config.parsePlatformTagName(tag),data,children, undefined,undefined,context ) }elseif((!data||!data.pre)&&isDef(Ctor=resolveAsset(context.$options,'components',tag))){ //component vnode=createComponent(Ctor,data,context,children,tag) }else{ //unknownorunlistednamespacedelements //checkatruntimebecauseitmaygetassignedanamespacewhenits //parentnormalizeschildren vnode=newVNode( tag,data,children, undefined,undefined,context ) } }else{ //directcomponentoptions/constructor vnode=createComponent(tag,data,context,children) } if(Array.isArray(vnode)){ returnvnode }elseif(isDef(vnode)){ if(isDef(ns))applyNS(vnode,ns) if(isDef(data))registerDeepBindings(data) returnvnode }else{ returncreateEmptyVNode() } }
creatElement最后结果时返回一个newVNode,并将craete时传入的参数,经过处理,传到VNode的初始化中,这里有几种情况,createEmptyVNode,没有传参数,或参数错误,会返回一个空的vnode,如果tag时浏览器的标签如divh3p等,会返回一个保留VNode,等等,最后,回到上面,vnode创建完毕,_render会返回这个vnode,最后走回vm._update(),update中,便是将vnode通过dom操作插入到真正的文档流中,下一节我们聊聊update
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。