一侈询、引子
在學習Vue的虛擬DOM的實現(xiàn)過程中,多次遇到“掛載”這個詞晚碾,偏偏在看到“掛載”這個詞的時候抓半,自己沒有辦法將這個詞對應的Vue生命周期具體的哪個部分,意識到自己對于生命周期的理解還是不夠深入格嘁,遂決定先加強學習一個$mount的這個過程笛求,查看了部分源碼以及擺渡了網(wǎng)上的很多文章,現(xiàn)做一點總結(jié)糕簿,下面這個簡約版的流程圖很短小精悍:二蜂嗽、探究生命周期
在創(chuàng)建一個vue實例的時候(var vm = new Vue(options))。Vue的構(gòu)造函數(shù)將自動運行 this._init(啟動函數(shù))殃恒。啟動函數(shù)的最后一步為initRender(vm):
// Vue.prototype._init
...
initLifecycle(vm);
initEvents(vm);
callHook(vm, 'beforeCreate');
initState(vm);
callHook(vm, 'created');
initRender(vm);
initRender中調(diào)用vm.options.el):
//initRender
...
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
將實例掛載到dom上植旧,至此啟動函數(shù)完成。
若Vue實例上面沒有el屬性离唐,則生命周期執(zhí)行到這就掛起了病附,直到手動去執(zhí)行vm.mount這個方法究竟完成了哪些事情完沪,這就是我們需要重點關注的。
三嵌戈、$mount函數(shù)的學習
當你去尋找mount的實現(xiàn)和平臺、環(huán)境都有關系宽档,主要是依據(jù)構(gòu)建方式區(qū)分為下面兩種:
(1)獨立構(gòu)建:包含模板編譯器
? 渲染過程: html字符串 → render函數(shù) → vnode → 真實dom節(jié)點
(2)運行時構(gòu)建: 不包含模板編譯器
? 渲染過程: render函數(shù) → vnode → 真實dom節(jié)點
可以看一下官網(wǎng)中的解釋:運行時 + 編譯器 vs. 只包含運行時
// 需要編譯器
new Vue({
template: '<div>{{ hi }}</div>'
})
// 不需要編譯器
new Vue({
render (h) {
return h('div', this.hi)
}
})
3.1 運行時+編譯器的$mount函數(shù)
學習的重點就是有編譯器的mount函數(shù)上面做了一層包裝,首先限制
el
不能為 body
雌贱、html
這類根節(jié)點啊送,接著,檢查是否有 render
方法欣孤,如果沒有則會把 el
或者 template
字符串轉(zhuǎn)換成 render
方法馋没,最后調(diào)用 compileToFunctions
方法實現(xiàn)render在線編譯,在確保已經(jīng)存在render函數(shù)的情況下降传,最后還是調(diào)用原型上面的mount函數(shù)篷朵。
const mount = Vue.prototype.$mount
Vue.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.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
...
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
...
}
return mount.call(this, el, hydrating)
}
3.2 原型上面的$mount函數(shù)
原型的$mount函數(shù)中最重要的是mountComponent 方法:
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
callHook(vm, 'beforeMount')
let updateComponent
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = 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)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
mountComponent
先調(diào)用 vm._render
方法先生成虛擬 Node,再實例化一個Watcher
婆排,由此看出声旺,渲染最核心的 2 個方法:vm._render
和 vm._update
。
Vue 的 _render
方法是實例的一個私有方法段只,可以把實例渲染成一個虛擬 Node腮猖,定義在 src/core/instance/render.js 中。平時開發(fā)工作中很少手寫 render
赞枕,大多是寫 template
模板澈缺,在上面的 mounted
方法中會把 template
編譯成 render
方法
Vue 的 _update
是實例的私有方法,它只在首次渲染和數(shù)據(jù)更新兩種情況下被調(diào)用炕婶,_update
方法把 VNode 渲染成真實的 DOM
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
這方法中最終實現(xiàn)將真實的DOM節(jié)點掛載到vm.$el上面姐赡,至此VUE生命周期中的掛載過程結(jié)束。
四柠掂、生命周期回顧
由上面的所有描述项滑,最終可以得到一個較為完整但是很精簡的流程圖:
最后附上一份VUE的生命周期圖,對照著整個掛載過程涯贞,一切都很清晰枪狂。
最最后,大家覺得有什么問題肩狂,或者有什么疑惑惡意給我留言摘完,覺得有所幫助的,記得點個贊哦I邓!