vue 掛載到dom 元素后發(fā)生了什么
專業(yè)領(lǐng)域包括網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站、商城網(wǎng)站制作、微信營銷、系統(tǒng)平臺(tái)開發(fā), 與其他網(wǎng)站設(shè)計(jì)及系統(tǒng)開發(fā)公司不同,創(chuàng)新互聯(lián)建站的整合解決方案結(jié)合了幫做網(wǎng)絡(luò)品牌建設(shè)經(jīng)驗(yàn)和互聯(lián)網(wǎng)整合營銷的理念,并將策略和執(zhí)行緊密結(jié)合,為客戶提供全網(wǎng)互聯(lián)網(wǎng)整合方案。
前一篇文章分析了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ù),正常開發(fā)過程中,對(duì)于dom的寫法有很多種,可以直接寫templete,也可以寫render函數(shù),也可以直接把dom寫在掛載元素里面,但是在編譯階段(通常是通過webpack執(zhí)行的),統(tǒng)統(tǒng)會(huì)把這些寫法都編譯成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 的類型定義,我們可以看到,_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è)谑謱憆ender函數(shù)時(shí),比如這樣
render:h=>{ return h( "div", 123 ) }
這時(shí)候我們使用的h 就是傳入的$createElement方法,然后我們來看一下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ù)層面的改變所以說代價(jià)很小,在這一過程中我們還可以進(jìn)行針對(duì)性的優(yōu)化,復(fù)用等,最后把優(yōu)化后的改變部分通過dom操作操作到真實(shí)的dom上去,另外,通過vdom這層的定義我們不僅僅可以把vdom映射到web文檔流上,甚至可以映射到app端的文檔流,桌面應(yīng)用的文檔流多種,這里引用一下vue js作者對(duì)vdom的評(píng)價(jià):Virtual DOM真正價(jià)值從來不是性能,而是它 1: 為函數(shù)式的ui編程方式打開了大門,2 :可以渲染到dom以外的backend 比如 ReactNative 。
下面我們來繼續(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)過處理,傳到VNode的初始化中,這里有幾種情況,createEmptyVNode,沒有傳參數(shù),或參數(shù)錯(cuò)誤,會(huì)返回一個(gè)空的vnode,如果tag 時(shí)瀏覽器的標(biāo)簽如div h4 p等,會(huì)返回一個(gè)保留VNode,等等,最后,回到上面,vnode 創(chuàng)建完畢,_render會(huì)返回這個(gè)vnode,最后走回vm._update(),update 中,便是將vnode 通過dom操作插入到真正的文檔流中,下一節(jié)我們聊聊update
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
分享標(biāo)題:詳解vue掛載到dom上會(huì)發(fā)生什么
路徑分享:http://jinyejixie.com/article8/jjjhop.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、商城網(wǎng)站、網(wǎng)站維護(hù)、域名注冊(cè)、電子商務(wù)、移動(dòng)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)