$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
結構體。