$mount
挂载入口的奥秘$mount
挂载入口的时候,但愿先了解一下关于我公众号14篇关于Vue合并策略的解析。这样能够更好的了解$mount挂载入口的区分意义合并策略已经讲解完成。在合并策略以后还有不少始化操做,在执始化执行到最后就是执行Vue原型上$mount
方法将组件挂载到开发者给定的元素之上。$mount
存在两种挂载方式,手动挂载
、一样在api
中向外暴露了。第二个则是自动挂载
,一旦有el
选项,则会在执行_init
最后进行内部的自动挂载。html
渲染挂载vue
首先第一个挂载入口在src/platforms/web/runtime/index.js
,在Vue
原型上挂载了$mount
函数。在src/platfroms/web/runtime/index.js
中进行了两个步骤的处理,第一个在合并策略中已经提到过,对平台进行区分重写,添加了一些针对于平台内置的components
,和directives
。第二个则是vue runtime-only
的版本,在此入口进行rollup
打包进以后是一个不通过编译的版本,可是须要经过打包工具把template
转成render
渲染函数。node
var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})
// 建立并挂载到 #app (会替换 #app)
new MyComponent().$mount('#app')
// 或者
new MyComponent().$mount(document.querySelector('#app'))
复制代码
$mount
文档中规定传入的el
参数能够是两种状况,{Element | string} [elementOrSelector]
,要么字符串
,要么是DOM元素
。经过$mount
进行挂载,会替换入的el
对应的DOM
无素。上面的DEMO
是经过手动调用$mount
进行挂载。一样建立实列的时候传入el进行自动挂载web
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
复制代码
渲染$mount
传入两个参数,一个是el
能够是一个DOM元素
,也能够是一个字符串
。api
el = el && inBrowser ? query(el) : undefined
复制代码
内部执行的第一部先拿到换化后的el
参数,由于el
多是字符串,可能会是Dom
元素,也有多是一个不符合参数要求的值。首先作两个判断,是否el
有值,而且此时执行的环境是浏览器环境浏览器
在 src/core/util/env文件中能够查看inBrowser的实现缓存
export const inBrowser = typeof window !== 'undefined'
复制代码
描述: 检查当前执行环境是不是浏览器环境bash
实现原理: 只有在浏览器环境中才会有
window
对象,经过typeof
去检测window
的类型,若是window
对象不存在,确定是undefined
闭包
知足了二者条件以后,通地query
方法去解析el
参数,获取到真正的DOM
元素,不然不知足二者条件,直接返回undefined
.app
query方法
/**
* Query an element selector if it's not an element already. */ export function query (el: string | Element): Element { if (typeof el === 'string') { const selected = document.querySelector(el) if (!selected) { process.env.NODE_ENV !== 'production' && warn( 'Cannot find element: ' + el ) return document.createElement('div') } return selected } else { return el } } 复制代码
描述: 经过无素选择器去获取元素
参数: el 能够是Dom无素,也能够是字符串
实现方式: 首选判断el
参数是不是字符串。是字符串使用document.querySelector
方法经过元素选择器去获取真正的dom
元素。若是获取不到,开发环境的状况下,发出'Cannot find element: ' + el警告。建立一个空的div
元素返回出去.el
参数不是字符串的状况下,不作任何操做直接返回el
参数。但在这个状况下还有两种可能,一个是不合法的值,比说传入了一个数字或布而值,或者传入了真正的DOM
元素(只考虑合并的状况)。
return mountComponent(this, el, hydrating)
复制代码
最后调用mountComponent
进行真正的挂载工做。最后返回的则是挂载后的组件实列。
const vm = new MyComponent().$mount('#app')
console.log(vm)
复制代码
经过$mount
渲染挂载以后执行mountComponent
以后返回了vm
实列,因此能够经过挂载后经过赋值给自定义一个变量,拿到最后挂载后的实列。
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
复制代码
在执行_init()
函数的最后,当初始化工做完成以后,el
通过全并已经合并到vm.$options
对象上,对$options
进行检测,若是存在el
则进行自动挂载,传入el
参数,调用是Vue
原型上$mount
函数。
若是此时运行的版本是runtime with compiler
版本,这个版本的$mount
会被进行重写。而且增长了把template
模板转成render
渲染函数。运行的入口在 src/platforms/web/entry-runtime-with-compiler
文件中。
缓存挂载的$mount
const mount = Vue.prototype.$mount
复制代码
前面分析的渲染挂载的$mount
在自执行的过程当中,比src/platforms/web/entry-runtime-with-compiler
文件中的$mount
先挂在Vue的原型上,负责在页面渲染真正的DOM
结构,经过mount
变量缓存了运行时版本的渲染挂载的函数。
Vue.prototype.$mount = function () {
...省略
}
复制代码
紧接着把Vue
原型上本来挂载的运行时版本的渲染挂载函数进行重写,这里重写的缘由主要由于这不可是一个运行时的版本,同时也担做着编译模版转化为render函数
的做用。此时针对了版本需求的不一样进行了重写。
el = el && query(el)
复制代码
对el
参数经过query
函数进行获取指入的挂载点,获取的Dom
元素赋值给el
参数,关于query
函数的运用已经解释过,若是不是元素选择器,则原封不动返回。
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.`
)
return this
}
复制代码
这里的判断挂载点是不是<body>
元素或者是<html>
元素,在生产环境下会报出警。不要挂载到html
和body
元素上,对其它元素进行替换。从Demo
理解真正缘由:
<body>
<p id="app">
</p>
</body>
var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})
const vm = new MyComponent().$mount('#app')
复制代码
合并结果, 对挂载元素进行审核:
<html>
<body>
<div>Hello</div>
</body>
</html>
复制代码
能够发现id
为app
的p
元素已经被MyComponent
组件模版被替换掉了,此时的挂载点只是一个被将要被替换的占位符。若是此时挂载点为body
元素或者html
元素的状况,body
和html
元素一样会被替换掉,此时html
页面则不是一个标准规定的html
标准体了。浏览器一样不会对此进行解析。
const options = this.$options
复制代码
声明options
变量,把初始化合并到实列对象上的$options
对象赋值给options
变量。
if (!options.render) {
}
return mount.call(this, el, hydrating)
复制代码
判断options
选项中是否有render
函数,既渲染函数。有则直接调用运行版本的$mount
函数,在以前运行时的$mount
函数已经缓存给了mount
变量。则直接经过mountComponent
方法进行渲染挂载,由此可知,渲染整个DOM
结构须要render
渲染函数作支撑。render
函数究竟是从那里来?为何有render
函数能够直接开始调用mountComponent
方法进行渲染。
vue-loader
把template
模版进行转化成render
函数。runtime-with-complier
版本,通过compileToFunctions
函数把template
模版编译成render
函数。ast语法转化入口解析。
let template = options.template
if (template) {
if (typeof template === '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
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
}
复制代码
在没有render
选项的状况。经过template
或者el
二者任意一个选项让模版进行转化成render
渲染函数,声明template
变量,经过options.template
选项赋值给template
变量。
- 先对
template
选项获取模版,当既有template
选项时,也有el
选项时,template
则优先做为转化render
函数的模版,el
则做为实例的挂载点。
若是有template
,再判断template
是不是字符串。字符串是否以id
为元素选择器。
字符串是元素选器
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
)
}
}
复制代码
经过charAt
方法匹配是否字符串首字符是#
号,调用idToToTemplate
函数,把元素选择器传入传为参数。
idToTemplate(template)
复制代码
描述:
经过元素选择符获取到元素,经过获取到的元素拿到内部的innerHTML
参数
template: 元素选择符
实现原理:
idToTemplate
内部经过闭包进行缓存转化后的模版。当执行idToTemplate
的时候引用了cached
执行的返回函数
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
复制代码
传入了一个参数为元素选择器的字符,内部则是利用query
函数获取对应转化后的元素。若是转化成功后返回元素内的innerHTML
关于cached
函数在合并策略中已经讲解过了。原理就是利用闭包的原理,传入一个纯函数,若是缓存对象上有已经缓存过的属性。由于id
选择器是惟一的,根据id
选择器转化后的属性和值会记录在缓存对象上,一旦再次获取一样的选择器的元素,能够经过缓存对象进行比对,一旦比对成功,则直接从缓存中获取。
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
复制代码
在开发环境中,若是经过id
选择器并无获取到对应的元素时。则会报错一个警告
tempalte
还能够直接传入node
节点,请看DEMO
<div id="app">
<div>
<p>{{a}}</p>
</div>
</div>
</body>
<script>
new Vue({
el: '#app',
template: document.querySelector('#app'),
data: {
a: 10
}
})
</script>
复制代码
若是是元素节点的分支的源码
else if (template.nodeType) {
template = template.innerHTML
}
复制代码
此时经过demo
能够看出此时tempalte
传入的是一个元素节点,代码运行时会跑入上面的分支代码,直接获取元素的innerHTML
做为模版
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
复制代码
若是template
既不是字符串也不是元素节点并在开发环境下,会报一个警告,请检查template
选项
template: `<div>
<p>{{a}}</p>
</div>`,
复制代码
在以上的可能性都已经分析过了。若是之前的状况都经过,则用转化为的template
模版,可是还有一种最经常使用的状况,当处理为字符串的时候,字符串开头并非以#开头,直接默认认为是开发者用模版字符串写入。以上这样子的写法一样生效。
ast解析转render渲染函数
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
复制代码
在各类状况下template
成功获取以后。经过compileToFunctions
进行ast
语法树转换,获得render泻染函数,赋值到实例的$options
选项上。
最后调用mount
缓存函数进行挂载,前面提到过若是同时有template
和el
选项,此时el
只会是一个挂载点。会优先根据template
选项生成真正的模版。
若是只存在el选项时,并无template选项。el既做为挂载点,也做为模版
若是没有template选项时,模版只会经过如下代码进行转换
else if (el) {
template = getOuterHTML(el)
}
复制代码
经过getOuterHTML
方法传入el
参数获取template
模版。
<div id="app">
<div>
<p>{{a}}</p>
</div>
</div>
</body>
<script>
new Vue({
el: document.querySelector('#app'),
data: {
a: 10
}
})
</script>
复制代码
/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
复制代码
首先判断el
元素是否有outerHTML
,正常的元素的outerHTML
则是传入el
元素自身。说明有些状况下元素会没有outerHTML
,从注示上能够看于对于ie浏览器中SVG无素是获取不到outerHTML
,此时就须要经过一个hack
处理,建立一个container
为div
的空元素,深度克隆el
元素,经过appendChild
方法把克隆后的el
元素添加到cantainer
容器中,成为子节点。最后返回的container
中的innerHTML
,这样的操做等同于获取了元素的outerHTML
.
在只有el
的状况下,又做为template
转化的模版,也要做为mountComponent
函数的替换元素的状况下,el
必须是一个Dom
元素。经过el
获取到了template
模版以后,调用compileToFunctions
转化成render
函数。最后调用缓存的mount
函数进行渲染Dom
结构体。