You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constidToTemplate=cached(id=>{constel=query(id)returnel&&el.innerHTML})constmount=Vue.prototype.$mountVue.prototype.$mount=function(el?: string|Element,hydrating?: boolean): Component{
el =el&&query(el)/* istanbul ignore if */if(el===document.body||el===document.documentElement){process.env.NODE_ENV!=='production'&&warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`)returnthis}constoptions=this.$options// resolve template/el and convert to render functionif(!options.render){lettemplate=options.templateif(template){if(typeoftemplate==='string'){if(template.charAt(0)==='#'){template=idToTemplate(template)/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&!template){warn(`Template element not found or is empty: ${options.template}`,this)}}}elseif(template.nodeType){template=template.innerHTML}else{if(process.env.NODE_ENV!=='production'){warn('invalid template option:'+template,this)}returnthis}}elseif(el){template=getOuterHTML(el)}if(template){/* istanbul ignore if */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/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){mark('compile end')measure(`vue ${this._name} compile`,'compile','compile end')}}}returnmount.call(this,el,hydrating)}
// public mount methodVue.prototype.$mount=function(el?: string|Element,hydrating?: boolean): Component{
el =el&&inBrowser ? query(el) : undefinedreturnmountComponent(this,el,hydrating)}
/** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */functiongetOuterHTML(el: Element): string{if(el.outerHTML){returnel.outerHTML}else{constcontainer=document.createElement('div')container.appendChild(el.cloneNode(true))returncontainer.innerHTML}}
getOuterHTML 判断传入的 el 有没有 outerHTML 方法,没有就把 el 外面包一层 div,然后 innerHTML,此时的 template 最终是一个字符串。
此刻, render 函数一定存在,然后 return 的 mount.call(this, el, hyrating) 中的 mount 就是之前缓存的 mount,也就是:
constmount=Vue.prototype.$mount
中的 mount,然后进行最开始的 Vue.prototype.$mount 方法:
// public mount methodVue.prototype.$mount=function(el?: string|Element,hydrating?: boolean): Component{
el =el&&inBrowser ? query(el) : undefinedreturnmountComponent(this,el,hydrating)}
exportfunctionmountComponent(vm: Component,el: ?Element,hydrating?: boolean): Component{vm.$el=elif(!vm.$options.render){vm.$options.render=createEmptyVNodeif(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')letupdateComponent/* istanbul ignore 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=()=>{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 definednewWatcher(vm,updateComponent,noop,{before(){if(vm._isMounted){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 hookif(vm.$vnode==null){vm._isMounted=truecallHook(vm,'mounted')}returnvm}
You are using the runtime-only build of Vue where the templatecompiler is not available. Either pre-compile the templates intorender functions, or use the compiler-included build.
紧接着后面,调用了一个 new Watcher 函数,它是一个 渲染watcher,记住这个点,一般在写代码的时候,watch被用来监听一些东西,所以这个 new Watcher 是一个和监听有关的强相关的一个类,也就是一个 观察者模式。代码中可以有很多自定义watcher,内部逻辑会有一个 渲染watcher。来看下这个 渲染watcher是干嘛的,在 src/core/observer/watcher.js 里,一个特别大的 watcher 定义:
/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */exportdefaultclassWatcher{vm: Component;expression: string;cb: Function;id: number;deep: boolean;user: boolean;computed: boolean;sync: boolean;dirty: boolean;active: boolean;dep: Dep;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=vmif(isRenderWatcher){vm._watcher=this}vm._watchers.push(this)// optionsif(options){this.deep=!!options.deepthis.user=!!options.userthis.computed=!!options.computedthis.sync=!!options.syncthis.before=options.before}else{this.deep=this.user=this.computed=this.sync=false}this.cb=cbthis.id=++uid// uid for batchingthis.active=truethis.dirty=this.computed// for computed watchersthis.deps=[]this.newDeps=[]this.depIds=newSet()this.newDepIds=newSet()this.expression=process.env.NODE_ENV!=='production'
? expOrFn.toString()
: ''// parse expression for getterif(typeofexpOrFn==='function'){this.getter=expOrFn}else{this.getter=parsePath(expOrFn)if(!this.getter){this.getter=function(){}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)}}if(this.computed){this.value=undefinedthis.dep=newDep()}else{this.value=this.get()}}/** * Evaluate the getter, and re-collect dependencies. */get(){pushTarget(this)letvalueconstvm=this.vmtry{value=this.getter.call(vm,vm)}catch(e){if(this.user){handleError(e,vm,`getter for watcher "${this.expression}"`)}else{throwe}}finally{// "touch" every property so they are all tracked as// dependencies for deep watchingif(this.deep){traverse(value)}popTarget()this.cleanupDeps()}returnvalue}/** * Add a dependency to this directive. */addDep(dep: Dep){constid=dep.idif(!this.newDepIds.has(id)){this.newDepIds.add(id)this.newDeps.push(dep)if(!this.depIds.has(id)){dep.addSub(this)}}}/** * Clean up for dependency collection. */cleanupDeps(){leti=this.deps.lengthwhile(i--){constdep=this.deps[i]if(!this.newDepIds.has(dep.id)){dep.removeSub(this)}}lettmp=this.depIdsthis.depIds=this.newDepIdsthis.newDepIds=tmpthis.newDepIds.clear()tmp=this.depsthis.deps=this.newDepsthis.newDeps=tmpthis.newDeps.length=0}/** * Subscriber interface. * Will be called when a dependency changes. */update(){/* istanbul ignore else */if(this.computed){// A computed property watcher has two modes: lazy and activated.// It initializes as lazy by default, and only becomes activated when// it is depended on by at least one subscriber, which is typically// another computed property or a component's render function.if(this.dep.subs.length===0){// In lazy mode, we don't want to perform computations until necessary,// so we simply mark the watcher as dirty. The actual computation is// performed just-in-time in this.evaluate() when the computed property// is accessed.this.dirty=true}else{// In activated mode, we want to proactively perform the computation// but only notify our subscribers when the value has indeed changed.this.getAndInvoke(()=>{this.dep.notify()})}}elseif(this.sync){this.run()}else{queueWatcher(this)}}/** * Scheduler job interface. * Will be called by the scheduler. */run(){if(this.active){this.getAndInvoke(this.cb)}}getAndInvoke(cb: Function){constvalue=this.get()if(value!==this.value||// Deep watchers and watchers on Object/Arrays should fire even// when the value is the same, because the value may// have mutated.isObject(value)||this.deep){// set new valueconstoldValue=this.valuethis.value=valuethis.dirty=falseif(this.user){try{cb.call(this.vm,value,oldValue)}catch(e){handleError(e,this.vm,`callback for watcher "${this.expression}"`)}}else{cb.call(this.vm,value,oldValue)}}}/** * Evaluate and return the value of the watcher. * This only gets called for computed property watchers. */evaluate(){if(this.dirty){this.value=this.get()this.dirty=false}returnthis.value}/** * Depend on this watcher. Only for computed property watchers. */depend(){if(this.dep&&Dep.target){this.dep.depend()}}/** * Remove self from all dependencies' subscriber list. */teardown(){if(this.active){// remove self from vm's watcher list// this is a somewhat expensive operation so we skip it// if the vm is being destroyed.if(!this.vm._isBeingDestroyed){remove(this.vm._watchers,this)}leti=this.deps.lengthwhile(i--){this.deps[i].removeSub(this)}this.active=false}}}
if(typeoftemplate==='string'){if(template.charAt(0)==='#'){template=idToTemplate(template)/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&!template){warn(`Template element not found or is empty: ${options.template}`,this)}}}
本篇说一下 Vue 的实例挂载,也就是
vm.$mount
都做了什么事情。打开
src/platforms/web/entry-runtime-with-compiler.js
可以看到有一个Vue.prototype.$mount
方法:可以看到
Vue.prototype.$mount
赋值给了mount
变量进行缓存,然后又重新定义了Vue.prototype.$mount
这个方法,最开始的Vue.prototype.$mount
是已经定义之后的,可以在src/platforms/web/runtime/index.js
中看到它的定义:那为什么又重新定义了一遍呢,是因为 Vue 有
Runtime-Complier
版本和Runtime-Only
版本:最开始的
Vue.prototype.$mount
是给Runtime-Only
版本使用的,所以在使用Runtime-Complier
版本的时候,需要把它给重写。还记得 Vue在初始化的时候有一个
vm.$mount(vm.$options.el)
么:这个里面的
vm.$mount(vm.$options.el)
实际上就是调用的重写之后的$mount
函数。来看下这个函数都做了事情:首先对传入的
el
参数进行处理,它可以是一个 String,也可以是一个 Element ,之后调用了query
方法,看下这个 query 方法做了什么事情:它调用了原生方法
document.querySelecto
来获取传入的el
,如果 el 是一个字符串,就调用这个原生方法获取 dom,如果找不到就返回一个空的 div,如果el
是个 dom 对象,就直接返回这个 dom 对象。此时返回的el
一定是一个 dom 对象。接着,拿到这个
el
以后,判断el
是不是 body 或者文档标签,如果是,就报一个错,说不可以把 Vue 挂载到<html>
或<body>
上。因为它是会覆盖的,如果可以挂在到
<html>
或者<body>
上的话,就会把整个 body 给替换掉! 所以我们一般使用一个 id 为 app 的方式去使用它然后拿到
options
,紧接着有一句if (!options.render)
,意思是判断有没有定义render
方法,接着判断有没有template
,以下写法定义一个template
是可以的:继续看它的源码逻辑,如果
template
是一个字符串,就对它做一点处理,如果是template.nodeType
也就是一个dom对象的话,就innerHTML
, 否则就会走一个getOuterHTML
方法:getOuterHTML
判断传入的el
有没有outerHTML
方法,没有就把el
外面包一层 div,然后innerHTML
,此时的template
最终是一个字符串。接着开始进行编译阶段,判断有没有
template
,大致就是拿到一个complieToFunctions
的render
函数,和一个staticRenderFns
函数,并且赋值。整体过一遍这个
$mount
做了事情:此刻,
render
函数一定存在,然后 return 的mount.call(this, el, hyrating)
中的mount
就是之前缓存的mount
,也就是:中的
mount
,然后进行最开始的Vue.prototype.$mount
方法:接着进行
mountComponent
方法,定义是在:src/core/instance/lifecycle.js
中:现在开始分析这个
mountComponent
做了什么事情:首先把
el
缓存给vm.$el
,然后判断有没有render
函数,如果没有或者没有将template
正常转为render
,就定义一个createEmptyVNode
,一个虚拟dom。接着判断在开发环境下的template
的第一个不是#
,就报一个错。简单说就是开发过程使用
Runtime-Only
版本的 Vue,然后使用了template
,但是没有使用render
函数,就会报一个错:或者使用了
Runtime-Complier
版本的Vue, 没有写template
,或者没有写render
函数,就会报一个错:这个错应该很熟悉吧。就是没有正确的
render
函数,所以报这个错,Vue 最终只认render
函数。接着,定义了一个
updateComponent
,有关mark
和performance
的判定先忽略,它是一些性能埋点的校验,一般情况下直接走最后:调用了
vm._update
方法,第一个参数是通过render
渲染出来一个VNode
,第二个参数是一个服务端渲染的参数,先忽略,默认为false。紧接着后面,调用了一个
new Watcher
函数,它是一个渲染watcher
,记住这个点,一般在写代码的时候,watch被用来监听一些东西,所以这个new Watcher
是一个和监听有关的强相关的一个类,也就是一个 观察者模式。代码中可以有很多自定义watcher,内部逻辑会有一个渲染watcher
。来看下这个 渲染watcher是干嘛的,在src/core/observer/watcher.js
里,一个特别大的 watcher 定义:看下传进去的参数都有哪些:
vm
,expOrFn
,cb
,option
,isRenderWatcher
。在上面,传入的参数有:
接着,判断
isRenderWatcher
是不是 true,也就是说,传进来的是不是一个 渲染watcher,如果是,就在vm
下添加一个_watcher
,然后把所有东西都 push 这个_watcher
里面。options
的判定先忽略,后面,定义了一个expression
,如果是在开发环境就expOrFn.toString()
。后面,判断
expOrFn
是不是一个函数,如果是,就赋值给getter
,否则调用parsePath
然后赋值给getter
。后面的this.computed
是有关计算属性的设置,先忽略。到value = this.getter.call(vm, vm)
这一步,这句会把刚才赋值的this.getter
调用,也就是刚才传入的updateComponent
被调用执行,也就是vm._update(vm._render(), hydrating)
会执行。vm._update
和vm._render
就是 最终挂载到真实dom 的函数。首先执行
vm._render
,还记得么,上面的render
最终生成了一个 VNode,然后调用_update
,把 VNode 传进去。至此,Vue实例就挂载好了。
总体来捋一遍:
Vue 实例挂载是通过
vm.prototype.$mount
实现的,先获取template
,template
的情况大致分为三种:直接写
template
template
是一个dom以及不写
template
,通过el
去获取template
接着把
template
通过一堆操作转化成render
函数,然后调用mountComponent
方法,里面定义了updateComponent
方法:然后将
updateComponent
扔到 渲染watcher(new Watcher) 里面,从而挂载成功!The text was updated successfully, but these errors were encountered: