详解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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。