您好,登錄后才能下訂單哦!
這篇文章主要介紹“Vue實例掛載的方法是什么”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Vue實例掛載的方法是什么”文章能幫助大家解決問題。
我們都聽過知其然知其所以然這句話。
那么不知道大家是否思考過new Vue()
這個過程中究竟做了些什么?
過程中是如何完成數據的綁定,又是如何將數據渲染到視圖的等等。
首先找到Vue
的構造函數
源碼位置: src/core/instance/index.js
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) }
options
是用戶傳遞過來的配置項,如data、methods
等常用的方法。
Vue
構建函數調用_init
方法,但我們發現本文件中并沒有此方法,但仔細可以看到文件下方定義了很多初始化方法。
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
方法,發現該方法在Vue
原型上定義了_init
方法。
源碼位置: src/core/instance/init.js
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // merge options // 合并屬性,判斷初始化的是否是組件,這里合并主要是 mixins 或 extends 的方法 if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. 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 // 初始化組件生命周期標志位 initLifecycle(vm) // 初始化組件事件偵聽 initEvents(vm) // 初始化渲染方法 initRender(vm) callHook(vm, 'beforeCreate') // 初始化依賴注入內容,在初始化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) } }
仔細閱讀上面的代碼,我們得到以下的結論:
在調用beforeCreate
之前,數據初始化并未完成,像data
、props
這些屬性無法訪問到
到了created
的時候,數據已經初始化完成,能夠訪問到data
、props
這些屬性,但這時候并未完成dom
的掛載,因此無法訪問到dom
元素。
掛載方法是調用vm.$mount
方法
initState
方法是完成 props/data/method/watch/methods
的初始化。
源碼位置:src/core/instance/state.js
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) } }
我們在這里主要看初始化data
的方法為initData
,它與initState
在同一文件上
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') { // 屬性名不能與方法名重復 if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } // 屬性名不能與state名稱重復 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)) { // 驗證key值的合法性 // 將_data中的數據掛載到組件vm上,這樣就可以通過this.xxx訪問到組件上的數據 proxy(vm, `_data`, key) } } // observe data // 響應式監聽data是數據的變化 observe(data, true /* asRootData */) }
仔細閱讀上面的代碼,我們可以得到以下結論:
初始化順序:props
、methods
、data
data
定義的時候可選擇函數形式或者對象形式(組件只能為函數形式)
關于數據響應式在這就不展示詳細說明了
上文提到掛載方法是調用vm.$mount
方法
源碼:
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { // 獲取或查詢元素 el = el && query(el) /* istanbul ignore if */ // vue 不允許直接掛載到body或頁面文檔上 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) { // 通過選擇器獲取元素內容 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轉換成render語法字符串 * 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) }
閱讀上面代碼,我們能得到以下結論:
不要將根元素放到body
或者html
上
可以在對象中定義template/render
或者直接使用template
、el
表示元素選擇器
最終都會解析成render
函數,調用compileToFunctions
,會將template
解析成render
函數
對template
的解析步驟大致分為以下幾步:
將html
文檔片段解析成ast
描述符
將ast
描述符解析成字符串
生成render
函數
生成render
函數,掛載到vm
上后,會再次調用mount
方法
源碼位置:src/platforms/web/runtime/index.js
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined // 渲染組件 return mountComponent(this, el, hydrating) }
調用mountComponent
渲染組件
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el // 如果沒有獲取解析的render函數,則會拋出警告 // 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 { // 沒有獲取到vue的模板文件 warn( 'Failed to mount component: template or render function not defined.', vm ) } } } // 執行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 { // 定義更新函數 updateComponent = () => { // 實際調?是在lifeCycleMixin中定義的_update和renderMixin中定義的_render 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 }
閱讀上面代碼,我們得到以下結論:
會觸發beforeCreate
鉤子
定義updateComponent
渲染頁面視圖的方法
監聽組件數據,一旦發生變化,觸發beforeUpdate
生命鉤子
updateComponent
方法主要執行在vue
初始化時聲明的render
, update
方法
render
的作用主要是生成vnode
源碼位置:src/core/instance/render.js
// 定義vue 原型上的render方法 Vue.prototype._render = function (): VNode { const vm: Component = this // render函數來自于組件的option const { render, _parentVnode } = vm.$options if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ) } // 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 { // There's no need to maintain a stack because all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm // 調用render方法,自己的獨特的render方法, 傳入createElement參數,生成vNode 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 } } 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
主要功能是調用patch
,將vnode
轉成為真實DOM
,并且更新到頁面中
源碼位置:src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode // 設置當前激活的作用域 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 // 執行具體的掛載邏輯 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. }
new Vue
的時候會調用_init
方法
定義$set
、$get
、$delete
、$watch
等方法
定義$on
、$off
、$emit
、$off
等事件
定義_update
、$forceUpdate
、$destory
生命周期
調用$mount
進行頁面的掛載
掛載的時候主要是通過mountComponent
方法
定義updateComponent
更新函數
執行render
生成虛擬DOM
_update
將虛擬DOM
生成真實DOM
結構,并且渲染到頁面中
關于“Vue實例掛載的方法是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。