站長資訊網(wǎng)
最全最豐富的資訊網(wǎng)站

Vue實(shí)例要怎么掛載?聊聊實(shí)例掛載的過程

Vue實(shí)例要怎么掛載?下面本篇文章帶大家了解一下Vue實(shí)例掛載的過程,希望對(duì)大家有所幫助。

Vue實(shí)例要怎么掛載?聊聊實(shí)例掛載的過程

Vue2目前已經(jīng)出現(xiàn)在大眾視野中非常久了,甚至Vue3都已經(jīng)開始投入使用,所有大家對(duì)于Vue2的掌握程度要逐步加深,即使你不想面大廠,也要試著到達(dá)源碼級(jí)的。Vue實(shí)例的掛載過程是一個(gè)面試中高頻出現(xiàn)的考點(diǎn),今天帶著大家根據(jù)源碼一步步探析!

一、思考

我們都聽過知其然知其所以然這句話。

那么不知道大家是否思考過new Vue()這個(gè)過程中究竟做了些什么?

過程中是如何完成數(shù)據(jù)的綁定,又是如何將數(shù)據(jù)渲染到視圖的等等。

二、分析

首先找到Vue的構(gòu)造函數(shù)

源碼位置: 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是用戶傳遞過來的配置項(xiàng),如data、methods等常用的方法。

Vue構(gòu)建函數(shù)調(diào)用_init方法,但我們發(fā)現(xiàn)本文件中并沒有此方法,但仔細(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方法。

源碼位置: 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     // 初始化組件生命周期標(biāo)志位     initLifecycle(vm)     // 初始化組件事件偵聽     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)     }   }

仔細(xì)閱讀上面的代碼,我們得到以下的結(jié)論:

  • 在調(diào)用beforeCreate之前,數(shù)據(jù)初始化并未完成,像data、props這些屬性無法訪問到
  • 到了created的時(shí)候,數(shù)據(jù)已經(jīng)初始化完成,能夠訪問到dataprops這些屬性,但這時(shí)候并未完成dom的掛載,因此無法訪問到dom元素。
  • 掛載方法是調(diào)用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)   } }

我們?cè)谶@里主要看初始化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') {       // 屬性名不能與方法名重復(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上,這樣就可以通過this.xxx訪問到組件上的數(shù)據(jù)       proxy(vm, `_data`, key)     }   }   // observe data   // 響應(yīng)式監(jiān)聽data是數(shù)據(jù)的變化   observe(data, true /* asRootData */) }

仔細(xì)閱讀上面的代碼,我們可以得到以下結(jié)論:

  • 初始化順序:props、methods、data
  • data定義的時(shí)候可選擇函數(shù)形式或者對(duì)象形式(組件只能為函數(shù)形式)

關(guān)于數(shù)據(jù)響應(yīng)式在這就不展示詳細(xì)說明了 上文提到掛載方法是調(diào)用vm.$mount方法

源碼:

Vue.prototype.$mount = function (   el?: string | Element,   hydrating?: boolean ): Component {   // 獲取或查詢?cè)?  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) {       // 通過選擇器獲取元素內(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語法字符串        *  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) }

閱讀上面代碼,我們能得到以下結(jié)論:

  • 不要將根元素放到body或者html
  • 可以在對(duì)象中定義template/render或者直接使用template、el表示元素選擇器
  • 最終都會(huì)解析成render函數(shù),調(diào)用compileToFunctions,會(huì)將template解析成render函數(shù)

對(duì)template的解析步驟大致分為以下幾步:

  • html文檔片段解析成ast描述符
  • ast描述符解析成字符串
  • 生成render函數(shù)

生成render函數(shù),掛載到vm上后,會(huì)再次調(diào)用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) }

調(diào)用mountComponent渲染組件

export function mountComponent (   vm: Component,   el: ?Element,   hydrating?: boolean ): Component {   vm.$el = el   // 如果沒有獲取解析的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 {         // 沒有獲取到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)     }   }   // 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   // 監(jiān)聽當(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    // 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 }

閱讀上面代碼,我們得到以下結(jié)論:

  • 會(huì)觸發(fā)beforeCreate鉤子
  • 定義updateComponent渲染頁面視圖的方法
  • 監(jiān)聽組件數(shù)據(jù),一旦發(fā)生變化,觸發(fā)beforeUpdate生命鉤子

updateComponent方法主要執(zhí)行在vue初始化時(shí)聲明的render, update方法

render的作用主要是生成vnode

源碼位置:src/core/instance/render.js

// 定義vue 原型上的render方法 Vue.prototype._render = function (): VNode {     const vm: Component = this     // render函數(shù)來自于組件的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         // 調(diào)用render方法,自己的獨(dú)特的render方法, 傳入createElement參數(shù),生成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主要功能是調(diào)用patch,將vnode轉(zhuǎn)成為真實(shí)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     // 設(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é)論

  • new Vue的時(shí)候會(huì)調(diào)用_init方法
    • 定義$set、$get、$delete$watch等方法
    • 定義$on$off、$emit、$off等事件
    • 定義_update$forceUpdate$destory生命周期
  • 調(diào)用$mount進(jìn)行頁面的掛載
  • 掛載的時(shí)候主要是通過mountComponent方法
  • 定義updateComponent更新函數(shù)
  • 執(zhí)行render生成虛擬DOM
  • _update將虛擬DOM生成真實(shí)DOM結(jié)構(gòu),并且渲染到頁面中

贊(0)
分享到: 更多 (0)
網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)
久久精品国产亚洲AV麻豆~| 香蕉久久综合精品首页| 精品亚洲国产成人av| 99国产精品免费视频观看| 国产成人A∨麻豆精品| 国产精品乱码一区二区三区 | 91精品福利在线观看| 青草青草久热精品视频在线网站 | 国产精品第一区第27页| 日韩欧美一区二区三区免费观看| 国产精品国产色综合色| 国产精品乱码一区二区三| 999久久久无码国产精品 | 精品国产亚洲一区二区三区在线观看 | 亚洲午夜精品久久久久久浪潮| 亚洲日韩国产精品乱-久| 又紧又大又爽精品一区二区| 国产精品特级露脸AV毛片| 精品露脸国产偷人在视频7| 精品人妻系列无码人妻漫画| 亚洲色精品VR一区区三区| 久久精品桃花综合| .精品久久久麻豆国产精品| 国产福利91精品一区二区三区 | 久久精品国产亚洲AV电影网| 久久只有这精品99| 久久99国产精品久久99小说| 国产精品吹潮香蕉在线观看| 人妖在线精品一区二区三区| 国产99视频精品一区| 久久亚洲精品专区蓝色区| 91精品免费在线观看| 无码国产精品一区二区免费式直播 | 波多野结衣久久精品| 亚洲精品无码专区在线播放| 国产精品久久新婚兰兰| 亚洲精品无码高潮喷水A片软| 久久99精品久久久久久水蜜桃| 嫩草影院精品视频在线观看| 国产精品国产精品偷麻豆| 国产成品精品午夜视频|