我们先关注一下$mount实现的是什么功能:我们打开源码路径core/instance/init.js:exportfunctioninitMixin(Vue:Class){......initLifecycle(vm)//事件侦听器初始化initEvents(vm)initRender(vm)callHook(vm,'beforeCreate')initInjections(vm)//resolveinjectionsbeforedata/props//initializevmstateprop/data/computed/watch完成初始化initState(vm)initProvide(vm)//resolveprovideafterdata/propscallHook(vm,'created')......//如果配置项中有el属性,则挂载到真正在DOM上,完成视图的渲染//这里的$mount方法本质上是调用了core/instance/liftcycle.js中的mountComponent方法if(vm.$options.el){vm.$mount(vm.$options.el)}}}这里怎么理解这个挂载状态呢?我们先来看Vue官方给出的一段描述。如果Vue实例在实例化时没有收到el选项,则它处于“未挂载”状态并且没有关联的DOM元素。可以使用vm.$mount()手动安装未安装的实例。如果没有提供elementOrSelector参数,模板将呈现为文档外部的元素。而且您必须使用本机DOMAPI将其插入到文档中。然后再看看$mount的内部机制:*缓存之前的$mount方法,以便后面返回实例,*/constmount=Vue.prototype.$mount/***手动挂载一个未挂载的根元素,并返回实例本身(Vueinstance)*/Vue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{el=el&&query(el)/*istanbulignoreif*//***挂载对象不能是body和html标签*/if(el===document.body||el===document.documentElement){process.env.NODE_ENV!=='production'&&warn(`不要将Vue挂载到或-而是挂载到普通元素。`)returnthis}constoptions=this.$options//resolvetemplate/elandconverttorenderfunction/***Determineif$optionsis有render方法*是:判断是String还是Element,获取它们的innerHTMl*无:Vue实例化时在vnode中创建一个空的注释节点见方法createEmptyVNode*/if(!options.render){lettemplate=options.templateif(template){if(typeoftemplate==='string'){if(template.charAt(0)==='#'){template=idToTemplate(template)/*istanbul忽略if*/if(process.env.NODE_ENV!=='production'&&!template){warn(`Templateelementnotfoundorisempty:${options.template}`,this)}}/***获取的Element的类型*详见https://developer.mozilla.org/zh-CN/docs/Web/API/Element/outerHTML*/}elseif(template.nodeType){template=template.innerHTML}else{if(process.env.NODE_ENV!=='production'){warn('无效模板选项:'+template,this)}returnthis}}elseif(el){template=getOuterHTML(el)}if(template){/*istanbulignoreif*//***用于监控编译性能*/if(process.env.NODE_ENV!=='production'&&config.performance&&mark){mark('compile')}//如果没有渲染函数,模板将被转换为渲染函数const{render,staticRenderFns}=compileToFunctions(template,{shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters:options.delimiters,comments:options.comments},this)options.render=renderoptions.staticRenderFns=staticRenderFns/*istanbulignoreif*//***用于监视器编译性能*/if(process.env.NODE_ENV!=='production'&&config.performance&&mark){mark('compileend')measure(`vue${this._name}compile`,'compile','compileend')}}}returnmount.call(this,el,hydrating)}和$mount实现mountComponent函数//publicmountmethodVue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):组件{el=el&&inBrowser?query(el):undefinedreturnmountComponent(this,el,hydrating)}然后我们再找mountComponent函数:exportfunctionmountComponent(vm:Component,el:?Element,hydrating?:boolean):Component{vm.$el=el//如果render函数不存在,则直接创建一个空间的VNode节点if(!vm.$options.render){vm.$options.render=createEmptyVNodeif(process.env.NODE_ENV!=='production'){/*istanbulignoreif*/if((vm.$options.template&&vm.$options.template.charAt(0)!=='#')||vm.$options.el||el){warn('您正在使用Vue的仅运行时版本,其中模板'+'编译器不可用。要么将模板预编译为'+'渲染函数,要么使用包含编译器的版本。',vm)}else{warn('Failedtomountcomponent:templateorrenderfunctionnotdefined.',vm)}}}//检测完成render后,开始调用beforeMount声明周callHook(vm,'beforeMount')letupdateComponent/*istanbul忽略if*/if(process.env.NODE_ENV!=='production'&&config.performance&&mark){updateComponent=()=>{constname=vm._nameconstid=vm._uidconststartTag=`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=()=>{//这里就是上面提到的观察者,这里注意第二个expOrFn参数是一个函数//newWatcher时会通过get方法执行一次//即Will触发第一个Dom更新vm._update(vm._render(),hydrating)}}vm._watcher=newWatcher(vm,updateComponent,noop,null,true/*isRenderWatcher*/)hydrating=false//触发$mount函数if(vm.$vnode==null){vm._isMounted=truecallHook(vm,'mounted')}returnvm}总结:执行vm._watcher=newWatcher(vm,updateComponent,noop)中触发get方法Watcher并设置Dep.target=watcher。执行更新组件。在这个过程中,我们会读取我们绑定的数据,因为我们之前已经通过Observer劫持了数据,会触发数据的get方法。这个时候watcher会被添加到对应的dep中。当有数据更新时,通过dep.notify()通知Watcher,然后执行Watcher中的update方法。此时会重新执行updateComponent,完成view的重新渲染。参考vue实战视频讲解:进入学习的时候,重点关注vm._update(vm._render(),hydrating):...letvnodetry{vnode=render.call(vm._renderProxy,vm.$createElement)}catch(e){handleError(e,vm,`render`)//返回错误渲染结果,//或前一个vnode以防止渲染错误导致空白组件/*istanbulignoreelse*/if(process.env.NODE_ENV!=='production'){if(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}}else{vnode=vm._vnode}}我们看到有两个方法vm._renderProxy代理vm来检测render是否使用了vm上面没有列出的属性和方法来报错,vm.$createElement是创建VNode:render:function(createElement){returncreateElement('h1','title')}我们知道的数据如何更新,然后是组件模板如何更新真实dom?解析模板生成字符串renderFunction处理字符串生成VNodepatchdiff算法处理VNode