VUE生命周期中的$mount掛載過程

一、引子

在學(xué)習(xí)Vue的虛擬DOM的實(shí)現(xiàn)過程中,多次遇到“掛載”這個(gè)詞,偏偏在看到“掛載”這個(gè)詞的時(shí)候,自己沒有辦法將這個(gè)詞對(duì)應(yīng)的Vue生命周期具體的哪個(gè)部分,意識(shí)到自己對(duì)于生命周期的理解還是不夠深入,遂決定先加強(qiáng)學(xué)習(xí)一個(gè)$mount的這個(gè)過程,查看了部分源碼以及擺渡了網(wǎng)上的很多文章,現(xiàn)做一點(diǎn)總結(jié),下面這個(gè)簡(jiǎn)約版的流程圖很短小精悍:
掛載的簡(jiǎn)約版流程.png

在我第一次看到這個(gè)流程圖的時(shí)候,其實(shí)內(nèi)心是一臉蒙蔽,所以下決心去好好看一下里面的運(yùn)行機(jī)制,下面是學(xué)習(xí)后對(duì)上面的流程圖進(jìn)行補(bǔ)充之后所畫:
掛載原創(chuàng)流程.png

二、探究生命周期

在創(chuàng)建一個(gè)vue實(shí)例的時(shí)候(var vm = new Vue(options))。Vue的構(gòu)造函數(shù)將自動(dòng)運(yùn)行 this._init(啟動(dòng)函數(shù))。啟動(dòng)函數(shù)的最后一步為initRender(vm):

// Vue.prototype._init
    ...
    initLifecycle(vm);
    initEvents(vm);
    callHook(vm, 'beforeCreate');
    initState(vm);
    callHook(vm, 'created');
    initRender(vm);

initRender中調(diào)用vm.mount(vm.options.el):

//initRender
  ...
  if (vm.$options.el) {
    vm.$mount(vm.$options.el);
  }

將實(shí)例掛載到dom上,至此啟動(dòng)函數(shù)完成。

若Vue實(shí)例上面沒有el屬性,則生命周期執(zhí)行到這就掛起了,直到手動(dòng)去執(zhí)行vm.mount(el),生命周期才會(huì)繼續(xù)執(zhí)行,接下來的重點(diǎn)就是mount這個(gè)方法究竟完成了哪些事情,這就是我們需要重點(diǎn)關(guān)注的。

三、$mount函數(shù)的學(xué)習(xí)

當(dāng)你去尋找mount函數(shù)的時(shí)候,你會(huì)發(fā)現(xiàn)不止一個(gè),具體的mount的實(shí)現(xiàn)和平臺(tái)、環(huán)境都有關(guān)系,主要是依據(jù)構(gòu)建方式區(qū)分為下面兩種:

(1)獨(dú)立構(gòu)建:包含模板編譯器

? 渲染過程: html字符串 → render函數(shù) → vnode → 真實(shí)dom節(jié)點(diǎn)

(2)運(yùn)行時(shí)構(gòu)建: 不包含模板編譯器

? 渲染過程: render函數(shù) → vnode → 真實(shí)dom節(jié)點(diǎn)

可以看一下官網(wǎng)中的解釋:運(yùn)行時(shí) + 編譯器 vs. 只包含運(yùn)行時(shí)

// 需要編譯器
new Vue({
  template: '<div>{{ hi }}</div>'
})

// 不需要編譯器
new Vue({
  render (h) {
    return h('div', this.hi)
  }
})

3.1 運(yùn)行時(shí)+編譯器的$mount函數(shù)

學(xué)習(xí)的重點(diǎn)就是有編譯器的mount函數(shù),該函數(shù)其實(shí)就是在運(yùn)行時(shí)的Vue實(shí)例原型上面的mount函數(shù)上面做了一層包裝,首先限制 el 不能為 body、html 這類根節(jié)點(diǎn),接著,檢查是否有 render 方法,如果沒有則會(huì)把 el 或者 template 字符串轉(zhuǎn)換成 render 方法,最后調(diào)用 compileToFunctions 方法實(shí)現(xiàn)render在線編譯,在確保已經(jīng)存在render函數(shù)的情況下,最后還是調(diào)用原型上面的mount函數(shù)。

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  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) {
    ...
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    ...
  }
  return mount.call(this, el, hydrating)
}

3.2 原型上面的$mount函數(shù)

原型的$mount函數(shù)中最重要的是mountComponent 方法:

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    ...
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  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)
    }
  }
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

mountComponent 先調(diào)用 vm._render 方法先生成虛擬 Node,再實(shí)例化一個(gè)Watcher,由此看出,渲染最核心的 2 個(gè)方法:vm._rendervm._update。

Vue 的 _render 方法是實(shí)例的一個(gè)私有方法,可以把實(shí)例渲染成一個(gè)虛擬 Node,定義在 src/core/instance/render.js 中。平時(shí)開發(fā)工作中很少手寫 render ,大多是寫 template 模板,在上面的 mounted 方法中會(huì)把 template 編譯成 render 方法

Vue 的 _update 是實(shí)例的私有方法,它只在首次渲染和數(shù)據(jù)更新兩種情況下被調(diào)用,_update 方法把 VNode 渲染成真實(shí)的 DOM

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm: Component = this
  const prevEl = vm.$el
  const prevVnode = vm._vnode
  const prevActiveInstance = activeInstance
  activeInstance = vm
  vm._vnode = vnode
  if (!prevVnode) {
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  } else {
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  activeInstance = prevActiveInstance
  if (prevEl) {
    prevEl.__vue__ = null
  }
  if (vm.$el) {
    vm.$el.__vue__ = vm
  }
  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
    vm.$parent.$el = vm.$el
  }
}

這方法中最終實(shí)現(xiàn)將真實(shí)的DOM節(jié)點(diǎn)掛載到vm.$el上面,至此VUE生命周期中的掛載過程結(jié)束。

四、生命周期回顧

由上面的所有描述,最終可以得到一個(gè)較為完整但是很精簡(jiǎn)的流程圖:

精簡(jiǎn)流程.png

最后附上一份VUE的生命周期圖,對(duì)照著整個(gè)掛載過程,一切都很清晰。

最最后,大家覺得有什么問題,或者有什么疑惑惡意給我留言,覺得有所幫助的,記得點(diǎn)個(gè)贊哦?。?/p>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容