一文了解Vue實(shí)例掛載的過(guò)程
new Vue()這個(gè)過(guò)程中究竟做了些什么?
function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) }
vue構(gòu)建函數(shù)調(diào)用_init方法,但發(fā)現(xiàn)本文件中并沒(méi)有此方法,但仔細(xì)可以看到文件下方定義了很多初始化方法
initMixin(Vue); // 定義 _init stateMixin(Vue); // 定義 $set $get $delete $watch 等 eventsMixin(Vue); // 定義事件 $on $once $off $emit lifecycleMixin(Vue);// 定義 _update $forceUpdate $destroy renderMixin(Vue); // 定義 _render 返回虛擬dom
首先可以看initMixin方法,發(fā)現(xiàn)該方法在Vue原型上定義了_init方法
Vue.prototype._init = function (options?: Object) { const vm: Component = this vm._uid = uid++ let startTag, endTag if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } vm._isVue = true // 合并屬性,判斷初始化的是否是組件,這里合并主要是 mixins 或 extends 的方法 if (options && options._isComponent) { initInternalComponent(vm, options) } else { // 合并vue屬性 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { // 初始化proxy攔截器 initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 初始化組件生命周期標(biāo)志位 initLifecycle(vm) // 初始化組件事件偵聽(tīng) initEvents(vm) // 初始化渲染方法 initRender(vm) callHook(vm, 'beforeCreate') // 初始化依賴注入內(nèi)容,在初始化data、props之前 initInjections(vm) // resolve injections before data/props // 初始化props/data/method/watch/methods initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } // 掛載元素 if (vm.$options.el) { vm.$mount(vm.$options.el) } }
在 init() 中 會(huì)通過(guò) callHook(vm,beforeCreate) 來(lái)執(zhí)行 beforeCreate 生命周期函數(shù)然后通過(guò)initState(vm) 初始化 props 、 methods 、data 接著會(huì)通過(guò)callHook(vm, 'created') 來(lái)執(zhí)行 created 生命周期函數(shù) 最后通過(guò) vm.$mount(vm.$options.el) 來(lái)掛載元素
所以:
1. 在beforeCreate 生命周期函數(shù)中是無(wú)法訪問(wèn) props和data 因?yàn)樗麄冞€沒(méi)有被初始化
2. 同理在created函數(shù)中可以訪問(wèn)props,methods、data數(shù)據(jù)同時(shí)也是最早可以調(diào)用接口的生
命周期函數(shù),但是此時(shí)dom并未掛載 所以無(wú)法訪問(wèn)dom元素
3. 在mounted中 此時(shí)dom已經(jīng)掛載成功 所以可以訪問(wèn)dom元素也是最早可以操作dom元素的生命周期函數(shù)
初始化數(shù)據(jù) initState(vm)
export function initState (vm: Component) { // 初始化組件的watcher列表 vm._watchers = [] const opts = vm.$options // 初始化props if (opts.props) initProps(vm, opts.props) // 初始化methods方法 if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { // 初始化data initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
在initState中會(huì)先通過(guò)initProps 、 initMethods 、 initData 先后分別來(lái)初始化 相關(guān)數(shù)據(jù) 在這里會(huì) 初始化組件的watcher列表
看下 initData
function initData (vm: Component) { let data = vm.$options.data // 獲取到組件上的data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { // 屬性名不能與方法名重復(fù) if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } // 屬性名不能與state名稱重復(fù) if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { // 驗(yàn)證key值的合法性 // 將_data中的數(shù)據(jù)掛載到組件vm上,這樣就可以通過(guò)this.xxx訪問(wèn)到組件上的數(shù)據(jù) proxy(vm, `_data`, key) } } // observe data // 響應(yīng)式監(jiān)聽(tīng)data是數(shù)據(jù)的變化 observe(data, true /* asRootData */) }
需要注意的是:初始化data數(shù)據(jù)的時(shí)候 會(huì)校驗(yàn) props中變量名稱和data中的不能重復(fù),在這里會(huì)通過(guò)observe劫持data的所有屬性,如果監(jiān)聽(tīng)到數(shù)據(jù)變化就通知訂閱者watcher來(lái)更新數(shù)據(jù),以此來(lái)實(shí)現(xiàn)數(shù)據(jù)雙向綁定 ,這就是vue實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式的 發(fā)布訂閱者模式 挖個(gè)坑自己實(shí)現(xiàn)該模式
再看下掛載方法是調(diào)用vm.$mount
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { // 獲取或查詢?cè)? el = el && query(el) /* istanbul ignore if */ // vue 不允許直接掛載到body或頁(yè)面文檔上 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) { let template = options.template // 存在template模板,解析vue模板文件 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 } } else if (el) { // 通過(guò)選擇器獲取元素內(nèi)容 template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } /** * 1.將temmplate解析ast tree * 2.將ast tree轉(zhuǎn)換成render語(yǔ)法字符串 * 3.生成render方法 */ const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating) }
在調(diào)用vm.$mount方法時(shí) 會(huì)將template 解析為 抽象語(yǔ)法樹(shù) (ast tree) 再將抽象語(yǔ)法樹(shù) 轉(zhuǎn)換成render語(yǔ)法字符串 最終生成render方法 掛載到vm上后,會(huì)再次調(diào)用mount方法
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined // 渲染組件 return mountComponent(this, el, hydrating) }
調(diào)用mountComponent渲染組件
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el // 如果沒(méi)有獲取解析的render函數(shù),則會(huì)拋出警告 // render是解析模板文件生成的 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 { // 沒(méi)有獲取到vue的模板文件 warn( 'Failed to mount component: template or render function not defined.', vm ) } } } // 執(zhí)行beforeMount鉤子 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 { // 定義更新函數(shù) updateComponent = () => { // 實(shí)際調(diào)?是在lifeCycleMixin中定義的_update和renderMixin中定義的_render vm._update(vm._render(), hydrating) } } // 監(jiān)聽(tīng)當(dāng)前組件狀態(tài),當(dāng)有數(shù)據(jù)變化時(shí),更新組件 new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { // 數(shù)據(jù)更新引發(fā)的組件更新 callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
此時(shí)會(huì)觸發(fā)beforeMount鉤子函數(shù) ,定義updateComponent來(lái)渲染頁(yè)面視圖的方法,監(jiān)聽(tīng)組件數(shù)據(jù),一旦發(fā)生變化,觸發(fā)beforeUpdate生命鉤子 最后執(zhí)行 callHook(vm, 'mounted') 鉤子函數(shù) 完成掛載 updateComponent方法主要執(zhí)行在vue初始化時(shí)聲明的render,update方法
render的作用主要是生成vnode
// 定義vue 原型上的render方法 Vue.prototype._render = function (): VNode { const vm: Component = this // render函數(shù)來(lái)自于組件的option const { render, _parentVnode } = vm.$options if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ) } vm.$vnode = _parentVnode // render self let vnode try { currentRenderingInstance = vm // 調(diào)用render方法,自己的獨(dú)特的render方法, 傳入createElement參數(shù),生成vNode vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { handleError(e, vm, `render`) 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 } } finally { currentRenderingInstance = null } // 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 }
_update主要功能是調(diào)用patch,將vnode轉(zhuǎn)換為真實(shí)DOM,并且更新到頁(yè)面中
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode // 設(shè)置當(dāng)前激活的作用域 const restoreActiveInstance = setActiveInstance(vm) vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // initial render // 執(zhí)行具體的掛載邏輯 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } restoreActiveInstance() // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
總結(jié)
到此這篇關(guān)于Vue實(shí)例掛載過(guò)程的文章就介紹到這了,更多相關(guān)Vue實(shí)例掛載過(guò)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue使用stompjs實(shí)現(xiàn)mqtt消息推送通知
這篇文章主要為大家詳細(xì)介紹了vue中使用stompjs實(shí)現(xiàn)mqtt消息推送通知,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06vue中調(diào)接口的方式詳解this.$api、直接調(diào)用、axios
這篇文章主要介紹了vue中調(diào)接口的方式:this.$api、直接調(diào)用、axios,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-11-11vue+openlayer5獲取當(dāng)前鼠標(biāo)滑過(guò)的坐標(biāo)實(shí)現(xiàn)方法
在vue項(xiàng)目中怎么獲取當(dāng)前鼠標(biāo)劃過(guò)的坐標(biāo)呢?下面通過(guò)本文給大家分享實(shí)現(xiàn)步驟,感興趣的朋友跟隨小編一起看看吧2021-11-11淺談el-table中使用虛擬列表對(duì)對(duì)表格進(jìn)行優(yōu)化
我們會(huì)經(jīng)常使用表格,如果數(shù)據(jù)量大就直接可以分頁(yè),如果多條可能會(huì)影響表格的卡頓,那么應(yīng)該如何進(jìn)行優(yōu)化,感興趣的可以了解一下2021-08-08elementui時(shí)間/日期選擇器選擇禁用當(dāng)前之前(之后)時(shí)間代碼實(shí)例
當(dāng)我們?cè)谶M(jìn)行網(wǎng)頁(yè)開(kāi)發(fā)時(shí),通常需要用到一些日期組件來(lái)方便用戶選擇時(shí)間,其中element日期組件是一個(gè)非常好用的工具,這篇文章主要給大家介紹了關(guān)于elementui時(shí)間/日期選擇器選擇禁用當(dāng)前之前(之后)時(shí)間的相關(guān)資料,需要的朋友可以參考下2024-02-02在vue項(xiàng)目中使用axios發(fā)送post請(qǐng)求出現(xiàn)400錯(cuò)誤的解決
這篇文章主要介紹了在vue項(xiàng)目中使用axios發(fā)送post請(qǐng)求出現(xiàn)400錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09vuejs使用FormData實(shí)現(xiàn)ajax上傳圖片文件
本篇文章主要介紹了vuejs使用FormData實(shí)現(xiàn)ajax上傳圖片文件,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08vue踩坑之backgroundImage路徑問(wèn)題及解決
這篇文章主要介紹了vue踩坑之backgroundImage路徑問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10Vite配置代理Proxy解決跨域問(wèn)題小結(jié)
我們?cè)谧鲰?xiàng)目的時(shí)候經(jīng)常會(huì)遇到跨域的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于Vite配置代理Proxy解決跨域問(wèn)題的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03