詳解vue掛載到dom上會(huì)發(fā)生什么
vue 掛載到dom 元素后發(fā)生了什么
前一篇文章分析了new vue() 初始化時(shí)所執(zhí)行的操作,主要包括調(diào)用vue._init 執(zhí)行一系列的初始化,包括生命周期,事件系統(tǒng),beforeCreate和Created hook,在在這里發(fā)生,重點(diǎn)分析了 initState,即對(duì)我們常用到的data props computed 等等進(jìn)行的初始化,最后,執(zhí)行$mount 對(duì)dom進(jìn)行了掛載,本篇文章將對(duì)掛載后所發(fā)生的事情進(jìn)行進(jìn)一步闡述,
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mount 的代碼很簡(jiǎn)單,直接執(zhí)行了moutComponent方法,
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (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')
let updateComponent
/* istanbul ignore if */
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)
}
}
// 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 defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
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 hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
moutComponent 這里判斷了render函數(shù),正常開(kāi)發(fā)過(guò)程中,對(duì)于dom的寫(xiě)法有很多種,可以直接寫(xiě)templete,也可以寫(xiě)render函數(shù),也可以直接把dom寫(xiě)在掛載元素里面,但是在編譯階段(通常是通過(guò)webpack執(zhí)行的),統(tǒng)統(tǒng)會(huì)把這些寫(xiě)法都編譯成render函數(shù),所以,最后執(zhí)行的都是render函數(shù),判斷完render可以看到,beforeMount hook在這里執(zhí)行,最后執(zhí)行了new Watcher() 我們進(jìn)入new Watcher
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
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 = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
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
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
其他方法暫時(shí)不提,可以看到,但是我們也能大致猜到他在做些什么,這里只是截取了部分Watcher的構(gòu)造方法,,重點(diǎn)是最后執(zhí)行了this.get 而this.get則執(zhí)行了this.getter,最后等于執(zhí)行了Watcher構(gòu)造方法中傳入的第二個(gè)參數(shù),也就是上一環(huán)節(jié)moutComponent中的updateComponent方法,updateComponent方法也是在moutComponent方法中定義
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
這里先是執(zhí)行編譯而成的render方法,然后作為參數(shù)傳到_update方法中執(zhí)行,render方法執(zhí)行后返回一個(gè)vnode 即Virtual dom,然后將這個(gè)Virtual dom作為參數(shù)傳到update方法中,這里我們先介紹一下Virtual dom 然后在介紹最后執(zhí)行掛載的update方法,
render函數(shù)
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
根據(jù)flow 的類(lèi)型定義,我們可以看到,_render函數(shù)最后返回一個(gè)vnode,_render主要代碼 在第一個(gè)try catch中,vnode = render.call(vm._renderProxy,vm.$CREATRElement) ,第一個(gè)參數(shù)為當(dāng)前上下文this 其實(shí)就是vm本身,第二個(gè)參數(shù)是實(shí)際執(zhí)行的方法,當(dāng)我們?cè)谑謱?xiě)render函數(shù)時(shí),比如這樣
render:h=>{
return h(
"div",
123
)
}
這時(shí)候我們使用的h 就是傳入的$createElement方法,然后我們來(lái)看一下createElement方法,在看creatElement之前,我們先簡(jiǎn)單介紹一下vdom,因?yàn)閏reateElement返回的就是一個(gè)vdom,vdom其實(shí)就是真實(shí)dom對(duì)象的一個(gè)映射,主要包含標(biāo)簽名字tag 和在它下面的標(biāo)簽 children 還有一些屬性的定義等等,當(dāng)我們?cè)谶M(jìn)行dom改變時(shí)首先是數(shù)據(jù)的改變,數(shù)據(jù)的改變映射到 vdom中,然后改變vdom,改變vdom是對(duì)js數(shù)據(jù)層面的改變所以說(shuō)代價(jià)很小,在這一過(guò)程中我們還可以進(jìn)行針對(duì)性的優(yōu)化,復(fù)用等,最后把優(yōu)化后的改變部分通過(guò)dom操作操作到真實(shí)的dom上去,另外,通過(guò)vdom這層的定義我們不僅僅可以把vdom映射到web文檔流上,甚至可以映射到app端的文檔流,桌面應(yīng)用的文檔流多種,這里引用一下vue js作者對(duì)vdom的評(píng)價(jià):Virtual DOM真正價(jià)值從來(lái)不是性能,而是它 1: 為函數(shù)式的ui編程方式打開(kāi)了大門(mén),2 :可以渲染到dom以外的backend 比如 ReactNative 。
下面我們來(lái)繼續(xù)介紹creatElement
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
if (!__WEEX__ || !('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
creatElement 最后結(jié)果時(shí)返回一個(gè)new VNode,并將craete時(shí)傳入的參數(shù),經(jīng)過(guò)處理,傳到VNode的初始化中,這里有幾種情況,createEmptyVNode,沒(méi)有傳參數(shù),或參數(shù)錯(cuò)誤,會(huì)返回一個(gè)空的vnode,如果tag 時(shí)瀏覽器的標(biāo)簽如div h3 p等,會(huì)返回一個(gè)保留VNode,等等,最后,回到上面,vnode 創(chuàng)建完畢,_render會(huì)返回這個(gè)vnode,最后走回vm._update(),update 中,便是將vnode 通過(guò)dom操作插入到真正的文檔流中,下一節(jié)我們聊聊update
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue 2.8.2版本配置剛進(jìn)入時(shí)候的默認(rèn)頁(yè)面方法
今天小編就為大家分享一篇vue 2.8.2版本配置剛進(jìn)入時(shí)候的默認(rèn)頁(yè)面方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
Vue $emit()不能觸發(fā)父組件方法的原因及解決
這篇文章主要介紹了Vue $emit()不能觸發(fā)父組件方法的原因及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07
vue中的keep-alive詳解與應(yīng)用場(chǎng)景
keep-alive是vue中的內(nèi)置組件,能在組件切換過(guò)程中將狀態(tài)保留在內(nèi)存中,防止重復(fù)渲染DOM,本文給大家介紹vue中的keep-alive詳解與應(yīng)用場(chǎng)景,感興趣的朋友一起看看吧2023-11-11
webstrom Debug 調(diào)試vue項(xiàng)目的方法步驟
這篇文章主要介紹了webstrom Debug 調(diào)試vue項(xiàng)目的方法步驟,詳細(xì)的介紹了兩種調(diào)試vue項(xiàng)目的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
vue 判斷兩個(gè)時(shí)間插件結(jié)束時(shí)間必選大于開(kāi)始時(shí)間的代碼
這篇文章主要介紹了vue 判斷兩個(gè)時(shí)間插件結(jié)束時(shí)間必選大于開(kāi)始時(shí)間的代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11

