上一篇中,我們一起探討了new Vue({...})背后發(fā)生了什么嚷量。那么當(dāng)我們實(shí)例化vue之后窒盐,進(jìn)行dom掛載又發(fā)生了什么呢糊肤?
image.png
細(xì)心的同學(xué)會(huì)發(fā)現(xiàn):$mount方法在多個(gè)文件中被定義巴席,如:
- src/platform/web/entry-runtime-with-compiler.js
- src/platform/web/runtime/index.js
- src/platform/weex/runtime/index.js
之所以有多個(gè)地方漾峡,是因?yàn)?mount實(shí)現(xiàn)是和平臺(tái)竹握、構(gòu)建方式都相關(guān)的
下面葱淳,我們選擇compiler版本分析
一. $mount 主干代碼如下:
Vue.prototype.$mount = function(el?: string | Element, hydrating?: boolean): Component {
el = el && query(el)
// query方法,實(shí)際上是對(duì)el參數(shù)做了一個(gè)轉(zhuǎn)化谦铃,el可能是string 或者 element耘成。如果是string,將返回document.querySelector(el)
// ...
const options = this.$options
if (!options.render) {
// render函數(shù)不存在
let template = options.template
if (template) {
// 如果存在template配置項(xiàng):
// 1. template可能是"#xx"驹闰,那么根據(jù)id獲取element內(nèi)容
// 2. 如果template存在nodeType瘪菌,那么獲取template.innerHTML 內(nèi)容
}else {
// 如果template配置項(xiàng)不存在template,但是存在el:
/*
* 例如: new Vue({
* el: "#app",
* ...
* })
*
*/
// 那么根據(jù)el獲取對(duì)應(yīng)的element內(nèi)容
}
// 經(jīng)過上面的處理嘹朗,將獲取的template做為參數(shù)調(diào)用compileToFunctions方法
// compileToFunctions方法會(huì)返回render函數(shù)方法师妙,render方法會(huì)保存到vm.$options下面
const { render, staticRenderFns } = compileToFunctions(template, {...})
options.render = render
}
return mount.call(this, el, hydrating)
}
從主干代碼我們可以看出做了以下幾件事
- 由于el參數(shù)有兩種類型,可能是string 或者 element骡显,調(diào)用query方法疆栏,統(tǒng)一轉(zhuǎn)化為Element類型
- 如果沒有手寫render函數(shù), 那么先獲取template內(nèi)容惫谤。再將template做為參數(shù)壁顶,調(diào)用compileToFunctions方法,返回render函數(shù)溜歪。
- 最后調(diào)用mount.call若专,這個(gè)方法實(shí)際上會(huì)調(diào)用runtime/index.js的mount方法
注:
- vue compiler分別2個(gè)版本:一個(gè)是構(gòu)建時(shí)版本,即我們使用vue-loader + webpack蝴猪。另一個(gè)版本是:運(yùn)行時(shí)版本调衰,運(yùn)行的時(shí)候,再去compiler解析自阱。我們這里分析的是 運(yùn)行時(shí)
- vue最終只認(rèn)render函數(shù)嚎莉,所以如果我們手動(dòng)寫render函數(shù),那么就直接調(diào)用mount.call沛豌。反之趋箩,vue會(huì)將template做為參數(shù),運(yùn)行時(shí)調(diào)用compileToFunctions方法加派,轉(zhuǎn)化為render函數(shù)叫确,再去調(diào)用mount.call方法。
- 如果是構(gòu)建時(shí)版本芍锦,vue-loader + webpack竹勉,會(huì)先將我們本地的代碼轉(zhuǎn)化成render函數(shù),運(yùn)行將直接調(diào)用mount.call娄琉。生產(chǎn)環(huán)境次乓,我們推薦構(gòu)建時(shí)的版本。個(gè)人學(xué)習(xí)推薦運(yùn)行時(shí)版本车胡。
- mount.call方法檬输,實(shí)際上會(huì)調(diào)用runtime/index.js下面的$mount方法,而這個(gè)方法很簡(jiǎn)單匈棘,將會(huì)調(diào)用mountComponent方法丧慈。
二. mountComponent 主干代碼如下:
export function mountComponent(vm: Component, el: ?Element, hydrating?: boolean): Component {
// ...
// 調(diào)用beforeMount生命周期函數(shù)
callHook(vm, 'beforeMount')
// ...
// 定義updateComonent方法
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// ...
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true)
// ...
// 調(diào)用生命周期函數(shù)mounted
callHook(vm, 'mounted')
}
Watch類相關(guān)代碼
Watch類有許多邏輯,這里我們只看和$mount相關(guān)的:
class Watcher {
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
){
// ...
if (typeof expOrFn === 'function') {
this.getter = expOrFn
}else {
// ...
}
// ...
this.value = this.lazy ? undefined : this.get()
}
get() {
// ...
value = this.getter.call(vm, vm)
// ...
// cleanupDeps方法后面我們會(huì)分析主卫,這個(gè)在性能優(yōu)化上比較重要
return value;
}
}
從上面代碼逃默,可以看出:
- 先調(diào)用beforeMount鉤子函數(shù)
- 將updateComponent方法做為參數(shù),實(shí)例化Watch簇搅。Watch在這個(gè)有2個(gè)作用:
1完域、初始化的時(shí)候會(huì)執(zhí)行回調(diào)函數(shù)
2、當(dāng) vm 實(shí)例中的監(jiān)測(cè)的數(shù)據(jù)發(fā)生變化的時(shí)候執(zhí)行回調(diào)函數(shù)
這里瘩将,我們先看第1個(gè)吟税。 第2個(gè)將在數(shù)據(jù)變化監(jiān)測(cè)章節(jié)分析
執(zhí)行回調(diào)后凹耙,我們看到vm._update(vm._render(), hydrating)方法,這個(gè)方法分2個(gè)步驟:
(1) 執(zhí)行render方法肠仪,返回最新的 VNode節(jié)點(diǎn)樹
(2) 調(diào)用update方法肖抱,實(shí)際上進(jìn)行diff算法比較,完成一次渲染 - 調(diào)用mounted鉤子函數(shù)
三. 總結(jié)
- options上無render函數(shù)异旧,對(duì)template, el做處理意述,獲取template內(nèi)容。
- 調(diào)用compileToFunctions方法吮蛹,獲取render函數(shù)荤崇,添加到options.render上
- 調(diào)用mount.call,實(shí)際上是調(diào)用mountComponent函數(shù)
- 調(diào)用beforeMount鉤子
- 實(shí)例化渲染watcher潮针,執(zhí)行回調(diào)
- 根據(jù)render函數(shù)獲取VNode節(jié)點(diǎn)樹 (其實(shí)是一個(gè)js對(duì)象)
- 執(zhí)行update方法术荤,實(shí)際上是patch過程,vue會(huì)執(zhí)行diff算法每篷,完成一次渲染
- 調(diào)用mounted鉤子
在下面的章節(jié)喜每,我們將陸續(xù)分析: 響應(yīng)式,compileToFunctions, 虛擬DOM雳攘,以及patch
碼字不易带兜,多多關(guān)注~??