vue 源碼學習 - 實例掛載

前言

在學習vue源碼以前須要先了解源碼目錄設計(瞭解各個模塊的功能)丶Flow語法。html

src
├── compiler    # 把模板解析成 ast 語法樹,ast 語法樹優化,代碼生成等功能。
├── core        # 核心代碼 Vue.js 的靈魂
├── platforms   # 不一樣平臺的支持 web 和 weex
├── server      # 服務端渲染這部分代碼是跑在服務端的 Node.js
├── sfc         # .vue 文件解析
├── shared      # 工具方法
複製代碼

flow語法能夠參照 v-model源碼學習中提到的flow語法介紹,以及到官網瞭解更多。vue

vue 實例化

vue 本質上就是一個用 Function 實現的 Class,而後它的原型 prototype 以及它自己都擴展了一系列的方法和屬性node

vue 的定義

在 src/core/instance/index.js 中git

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
複製代碼

經過源碼咱們能夠看到,它實際上就是一個構造函數。咱們日後看這裏有不少 xxxMixin 的函數調用,並把 Vue 當參數傳入,它們的功能都是給 Vue 的 prototype 上擴展一些方法。github

階段

  1. 首先經過new Vue實例化,過程能夠參考以前寫的vue 生命週期梳理
  2. vue 實例掛載的實現 Vue中是經過$mount實例方法去掛載vm,$mount方法再多個文件中都有定義,和平臺,構建方式相關。 首先來看 src/platform/web/entry-runtime-with-compiler.js文件中
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  // A-> ..... 表明後面省略的代碼從A-> 處接下去
}
複製代碼

1.這段代碼首先緩存了原型上的$mount 方法,再從新定義該方法
爲了對比先後方法的差異,咱們能夠先看web

compiler 版本的 $mount

\$mount 方法
2. $mount方法支持傳入兩個參數,第一個是el,它表示掛載的元素,能夠是字符串,能夠是DOM對象,會調用 query方法轉換成DOM對象,在瀏覽器環境下咱們不須要傳第二個參數,它是一個可選參數。
接下來繼續看後面的代碼

// <-A ..... 表明接前面的代碼繼續寫
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
  // A-> ..... 表明後面省略的代碼從A-> 處接下去
複製代碼

首先對 el 作了限制,Vue 不能掛載在 body、html 這樣的根節點上。若是是其中一個則返回this。this就是vue實例自己 數組

vuethis

定義option對象(new Vue中傳入的數據)

// <-A ..... 表明接前面的代碼繼續寫
if (!options.render) {
    let template = options.template
     if (template) {
        // B-> ..... 表明後面省略的代碼從B-> 處接下去
     }else if(el){
        // C-> ..... 表明後面省略的代碼從C-> 處接下去
     }
      if (template) {
        // D-> ..... 表明後面省略的代碼從D-> 處接下去
      }
    return mount.call(this, el, hydrating)
}
複製代碼
  1. 判斷有沒有定義render方法,沒有則會把el或者template字符串轉換成render方法。在 Vue 2.0 版本中,全部 Vue 的組件的渲染最終都須要 render 方法
// <-B ..... 表明接前面的代碼繼續寫
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
          }
}

複製代碼
  1. 判斷template 是否爲字符串,取字符串的第一位判斷是不是# 若是是#開頭表明節點字符串,並調用idToTemplate方法以下
const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})
複製代碼

接受一個參數,對這個參數進行query方法,前面提到query是將字符串轉化成DOM,而且返回DOM的innerHTML瀏覽器

// <-C ..... 表明接前面的代碼繼續寫
  template = getOuterHTML(el)
複製代碼

若是沒有render和template的狀況下,使用getOuterHTML方法從新定義template緩存

function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}
複製代碼
  1. 掛在DOM元素的HTML會被提取出來用做模板

outerHTML
總結 : render函數優先級最高,template和el次之

模板類型

  1. render : 類型function 接收一個 createElement 方法做爲第一個參數用來建立 VNode
render: function (createElement) {
    return createElement(
      'h' + this.level,   // 標籤名稱
      this.$slots.default // 子元素數組
    )
  },
複製代碼
  1. template:類型string 一個字符串模板做爲 Vue 實例的標識使用。模板將會 替換 掛載的元素。
  2. el:類型string | HTMLElement 提供一個在頁面上已存在的 DOM 元素做爲 Vue 實例的掛載目標。能夠是 CSS 選擇器,也能夠是一個 HTMLElement 實例

runtime only 版本的$mount

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
複製代碼

開始和compiler版本的$mount 實現相同,只不過多加了一個inBrowser判斷是否在瀏覽器環境下。
$mount 方法實際上會去調用 mountComponent 方法,這個方法定義在 src/core/instance/lifecycle.js 文件中bash

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
    vm.$el = el
    if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode
    }
    callHook(vm, 'beforeMount')
    // A-> ..... 表明後面省略的代碼從A-> 處接下去
}
複製代碼
  1. mountComponent接收到Vue.prototype.$mount方法中vue實例對象,和el字符串(通過query處理已經轉成DOM)
  2. 更新vm實例上的$el
  3. 判斷vm上有無render模板,若是沒有建立一個空的虛擬VNode
  4. 插入beforeMount鉤子
// <-A ..... 表明接前面的代碼繼續寫
 let updateComponent
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  }else{
     updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }  
  }
 vm._watcher = new Watcher(vm, updateComponent, noop)
 // A-> ..... 表明後面省略的代碼從A-> 處接下去
複製代碼
  1. mountComponent 核心就是先調用vm._render方法先生成虛擬 Node 將 vm._update方法做爲返回值賦值給updateComponent
  2. 實例化Watcher構造函數,將updateComponent做爲回調函數,也就是說在實例化Watcher後最終調用vm._update 更新 DOM。

watcher的做用

  1. 實例化的過程後執行回調,將調用vm._update 更新 DOM。
  2. vm 實例中的監測的數據發生變化的時候執行回調函數實現更新DOM
// <-A ..... 表明接前面的代碼繼續寫
hydrating = false
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
複製代碼

這裏vm.$vnode的值是什麼,文件定義在src/core/instance/render.js 中,這裏只關注vm.$vnode因此貼出相關代碼

export function renderMixin (Vue: Class<Component>) {
    Vue.prototype._render = function (): VNode {
        const vm: Component = this
        const { render, _parentVnode } = vm.$options
        vm.$vnode = _parentVnode
    }
}
複製代碼

renderMixin函數接收Vue實例參數,在vue原型上的內部_render方法須要返回一個VNode,而且經過結構賦值的方法取出實例中$options的屬性和方法。
咱們來看看vm.$options對象具體有些什麼

parent

  1. 對象中有render函數,可是還未定義_parentVnode。能夠知道vm.$vnode 表示 Vue 實例的父虛擬 Node,並且在mountComponent 函數中值還未定義。
  2. 因爲未定義vm.$vnode值爲undefined 因此vm.$vnode==null結果也爲真
    mount
  3. 咱們也能夠經過生命週期圖來理解, VNode render 是發生在beforeUPdate 以後updated以前這個環節
  4. 流程 :(1) new Vue ==> (2) init ==> (3) $mount ==> (4) compile ==> (5) render ==> (6) vnode ==> (7) patch ==> (8) DOM
  5. 最後設置 vm._isMounted 爲 true做爲以後判斷是否經歷了mounted生命週期的條件

總結

  1. 判斷掛載的節點不能掛載在 body、html 上。
  2. 模板優先級render>template>el 而且最終都會轉換成render方法
  3. 知道mountComponent方法 作了什麼,先是調用了vm._render 方法先生成虛擬 Node,而後實例化Watcher 執行它,並監聽數據變化,實時更新。
  4. 設置vm._isMounted標誌,做爲判斷依據
相關文章
相關標籤/搜索