Vue 源碼深入解析之 Flow 流程準備、源碼設計、源碼構建和入口配置

一、認識 Flow

  1. Flowfacebook 出品的 JavaScript 靜態類型檢查工具,和 Typescript 功能類型。Vue.js 的源碼利用了 Flow 做了靜態類型檢查,所以瞭解 Flow 有助於我們閱讀源碼。

  2. 使用 Flow 的原因,如下所示:

  • JavaScript 是動態類型語言,它的靈活性有目共睹,但是過於靈活的副作用是很容易就寫出非常隱蔽的隱患代碼,在編譯期甚至看上去都不會報錯,但在運行階段就可能出現各種奇怪的 bug

  • 類型檢查是當前動態類型語言的發展趨勢,所謂類型檢查,就是在編譯期儘早發現(由類型錯誤引起的)bug,又不影響代碼運行(不需要運行時動態檢查類型),使編寫 JavaScript 具有和編寫 Java 等強類型語言相近的體驗。

  • 項目越複雜就越需要通過工具的手段來保證項目的維護性和增強代碼的可讀性。 Vue.js 在做 2.0 重構的時候,在 ES2015 的基礎上,除了 ESLint 保證代碼風格之外,也引入了 Flow 做靜態類型檢查。之所以選擇 Flow,主要是因爲 BabelESLint 都有對應的 Flow 插件以支持語法,可以完全沿用現有的構建配置,非常小成本的改動就可以擁有靜態類型檢查的能力。

  1. Flow 的工作方式,通常類型檢查分成兩種方式,如下所示:
  • 類型推斷:通過變量的使用上下文來推斷出變量類型,然後根據這些推斷來檢查類型。

  • 類型註釋:事先註釋好我們期待的類型,Flow 會基於這些註釋來判斷。

  1. 類型推斷:它不需要任何代碼修改即可進行類型檢查,最小化開發者的工作量。它不會強制你改變開發習慣,因爲它會自動推斷出變量的類型。這就是所謂的類型推斷,Flow 最重要的特性之一。可以通過一個簡單例子說明一下:

    /*@flow*/
    
    function split(str) {
      return str.split(' ')
    }
    
    split(11)
    

    Flow 檢查上述代碼後會報錯,因爲函數 split
    期待的參數是字符串,而我們輸入了數字。

  2. 類型註釋:類型推斷是 Flow 最有用的特性之一,不需要編寫類型註釋就能獲取有用的反饋。但在某些特定的場景下,添加類型註釋可以提供更好更明確的檢查依據,考慮如下代碼:

    /*@flow*/
    
    function add(x, y){
      return x + y
    }
    
    add('Hello', 11)
    

    Flow 檢查上述代碼時檢查不出任何錯誤,因爲從語法層面考慮, + 既可以用在字符串上,也可以用在數字上,我們並沒有明確指出 add() 的參數必須爲數字。

    在這種情況下,我們可以藉助類型註釋來指明期望的類型。類型註釋是以冒號 : 開頭,可以在函數參數,返回值,變量聲明中使用。

    如果我們在上段代碼中添加類型註釋,就會變成如下:

    /*@flow*/
    
    function add(x: number, y: number): number {
      return x + y
    }
    
    add('Hello', 11)
    

    現在 Flow 就能檢查出錯誤,因爲函數參數的期待類型爲數字,而我們提供了字符串。

上面的例子是針對函數的類型註釋。接下來我們來看看 Flow 能支持的一些常見的類型註釋,如下所示:

  • 數組,如下所示:
/*@flow*/

var arr: Array<number> = [1, 2, 3]

arr.push('Hello')

數組類型註釋的格式是 Array<T>T 表示數組中每項的數據類型。在上述代碼中,arr 是每項均爲數字的數組。如果我們給這個數組添加了一個字符串,Flow 能檢查出錯誤。

  • 類和對象,如下所示:
/*@flow*/

class Bar {
  x: string;           // x 是字符串
  y: string | number;  // y 可以是字符串或者數字
  z: boolean;

  constructor(x: string, y: string | number) {
    this.x = x
    this.y = y
    this.z = false
  }
}

var bar: Bar = new Bar('hello', 4)

var obj: { a: string, b: number, c: Array<string>, d: Bar } = {
  a: 'hello',
  b: 11,
  c: ['hello', 'world'],
  d: new Bar('hello', 3)
}

類的類型註釋格式如上,可以對類自身的屬性做類型檢查,也可以對構造函數的參數做類型檢查。這裏需要注意的是,屬性 y 的類型中間用 | 做間隔,表示 y 的類型即可以是字符串也可以是數字。對象的註釋類型類似於類,需要指定對象屬性的類型。

  • Null,如下所示:

若想任意類型 T 可以爲 null 或者 undefined,只需類似如下寫成 ?T 的格式即可。

/*@flow*/

var foo: ?string = null

此時,foo 可以爲字符串,也可以爲 null

  1. FlowVue.js 源碼中的應用,如下所示:
  • 有時候我們想引用第三方庫,或者自定義一些類型,但 Flow 並不認識,因此檢查的時候會報錯。爲了解決這類問題,Flow 提出了一個 libdef 的概念,可以用來識別這些第三方庫或者是自定義類型,而 Vue.js 也利用了這一特性。

  • Vue.js 的主目錄下有 .flowconfig 文件, 它是 Flow 的配置文件,可以看官方文檔。這其中的 [libs] 部分用來描述包含指定庫定義的目錄,默認是名爲 flow-typed 的目錄。

  • 這裏 [libs] 配置的是 flow,表示指定的庫定義都在 flow 文件夾內。我們打開這個目錄,會發現文件如下:

flow
├── compiler.js        # 編譯相關
├── component.js       # 組件數據結構
├── global-api.js      # Global API 結構
├── modules.js         # 第三方庫定義
├── options.js         # 選項相關
├── ssr.js             # 服務端渲染相關
├── vnode.js           # 虛擬 node 相關
  • 由上可以看到,Vue.js 有很多自定義類型的定義,在閱讀源碼的時候,如果遇到某個類型並想了解它完整的數據結構的時候,可以回來翻閱這些數據結構的定義。
  1. 總結:通過對 Flow 的認識,有助於我們閱讀 Vue 的源碼,並且這種靜態類型檢查的方式非常有利於大型項目源碼的開發和維護。

二、Vue.js 源碼目錄設計

  1. Vue.js 的源碼都在 src 目錄下,其目錄結構如下所示:
src
├── compiler        # 編譯相關 
├── core            # 核心代碼 
├── platforms       # 不同平臺的支持
├── server          # 服務端渲染
├── sfc             # .vue 文件解析
├── shared          # 共享代碼
  1. compiler,如下所示:
  • compiler 目錄包含 Vue.js 所有編譯相關的代碼。它包括把模板解析成 ast 語法樹,ast 語法樹優化,代碼生成等功能
  • 編譯的工作可以在構建時做(藉助 webpack、vue-loader 等輔助插件);也可以在運行時做,使用包含構建功能的 Vue.js。顯然,編譯是一項耗性能的工作,所以更推薦前者——離線編譯
  1. core,如下所示:
  • core 目錄包含了 Vue.js 的核心代碼,包括內置組件、全局 API 封裝,Vue 實例化、觀察者、虛擬 DOM、工具函數等等,這裏的代碼是 Vue.js 的靈魂
  1. platform,如下所示:
  • Vue.js 是一個跨平臺的 MVVM 框架,它可以跑在 web 上,也可以配合 weex 跑在 native 客戶端上。platformVue.js 的入口,2 個目錄代表 2個主要入口,分別打包成運行在 web 上和 weex 上的 Vue.js,我們會重點分析 web 入口打包後的 Vue.js
  1. server,如下所示:
  • Vue.js 2.0 支持了服務端渲染,所有服務端渲染相關的邏輯都在這個目錄下。注意:這部分代碼是跑在服務端的 Node.js,不要和跑在瀏覽器端的 Vue.js 混爲一談

  • 服務端渲染主要的工作是把組件渲染爲服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最後將靜態標記"混合"爲客戶端上完全交互的應用程序

  1. sfc,如下所示:
  • 通常我們開發 Vue.js 都會藉助 webpack 構建, 然後通過 .vue 單文件來編寫組件,這個目錄下的代碼邏輯會把 .vue 文件內容解析成一個 JavaScript 的對象
  1. shared,如下所示:
  • Vue.js 會定義一些工具方法,這裏定義的工具方法都是會被瀏覽器端的 Vue.js 和服務端的 Vue.js 所共享的
  1. 總結:從 Vue.js 的目錄設計可以看到,把功能模塊拆分的非常清楚,相關的邏輯放在一個獨立的目錄下維護,並且把複用的代碼也抽成一個獨立目錄。這樣的目錄設計讓代碼的閱讀性和可維護性都變強,是非常值得學習和推敲的。

三、Vue.js 源碼構建

  1. Vue.js 源碼是基於 Rollup 構建的,它的構建相關配置都在 scripts 目錄下。

  2. 構建腳本,如下所示:

  • 通常一個基於 NPM 託管的項目都會有一個 package.json 文件,它是對項目的描述文件,它的內容實際上是一個標準的 JSON 對象
  • 我們通常會配置 script 字段作爲 NPM 的執行腳本,Vue.js 源碼構建的腳本如下:
{
  "script": {
    "build": "node scripts/build.js",
    "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
    "build:weex": "npm run build -- weex"
  }
}
 

這裏總共有 3 條命令,作用都是構建 Vue.js,後面 2 條是在第一條命令的基礎上,添加一些環境參數。當在命令行運行 npm run build 的時候,實際上就會執行 node scripts/build.js,接下來我們來看看它實際是怎麼構建的

  1. 構建過程,如下所示:
  • 我們對於構建過程分析是基於源碼的,先打開構建的入口 JS 文件,在 scripts/build.js 中:
let builds = require('./config').getAllBuilds()

// filter builds via command line arg
if (process.argv[2]) {
  const filters = process.argv[2].split(',')
  builds = builds.filter(b => {
    return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
  })
} else {
  // filter out weex builds by default
  builds = builds.filter(b => {
    return b.output.file.indexOf('weex') === -1
  })
}

build(builds)

這段代碼邏輯非常簡單,先從配置文件讀取配置,再通過命令行參數對構建配置做過濾,這樣就可以構建出不同用途的 Vue.js 了。接下來我們看一下配置文件,在 scripts/config.js 中:

const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.js'),
    format: 'cjs',
    banner
  },
  // Runtime+compiler CommonJS build (CommonJS)
  'web-full-cjs': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.js'),
    format: 'cjs',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime only (ES Modules). Used by bundlers that support ES Modules,
  // e.g. Rollup & Webpack 2
  'web-runtime-esm': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.esm.js'),
    format: 'es',
    banner
  },
  // Runtime+compiler CommonJS build (ES Modules)
  'web-full-esm': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.js'),
    format: 'es',
    alias: { he: './entity-decoder' },
    banner
  },
  // runtime-only build (Browser)
  'web-runtime-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.js'),
    format: 'umd',
    env: 'development',
    banner
  },
  // runtime-only production build (Browser)
  'web-runtime-prod': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.min.js'),
    format: 'umd',
    env: 'production',
    banner
  },
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime+compiler production build  (Browser)
  'web-full-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.min.js'),
    format: 'umd',
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // ...
}
  • 這裏列舉了一些 Vue.js 構建的配置,關於還有一些服務端渲染 webpack 插件以及 weex 的打包配置就不列舉了。

  • 對於單個配置,它是遵循 Rollup 的構建規則的。其中 entry 屬性表示構建的入口 JS 文件地址,dest 屬性表示構建後的JS 文件地址。format 屬性表示構建的格式,cjs 表示構建出來的文件遵循 CommonJS 規範,es 表示構建出來的文件遵循 ES Module 規範。 umd 表示構建出來的文件遵循 UMD 規範。

  • web-runtime-cjs 配置爲例,它的 entry
    resolve('web/entry-runtime.js'),先來看一下 resolve 函數的定義,源碼目錄:scripts/config.js,如下所示:

const aliases = require('./alias')
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

這裏的 resolve 函數實現非常簡單,它先把 resolve 函數傳入的參數 p 通過 / 做了分割成數組,然後取數組第一個元素設置爲 base。在我們這個例子中,參數 pweb/entry-runtime.js,那麼 base 則爲 webbase 並不是實際的路徑,它的真實路徑藉助了別名的配置,我們來看一下別名配置的代碼,在 scripts/alias 中:

const path = require('path')

module.exports = {
  vue: path.resolve(__dirname, '../src/platforms/web/entry-runtime-with-compiler'),
  compiler: path.resolve(__dirname, '../src/compiler'),
  core: path.resolve(__dirname, '../src/core'),
  shared: path.resolve(__dirname, '../src/shared'),
  web: path.resolve(__dirname, '../src/platforms/web'),
  weex: path.resolve(__dirname, '../src/platforms/weex'),
  server: path.resolve(__dirname, '../src/server'),
  entries: path.resolve(__dirname, '../src/entries'),
  sfc: path.resolve(__dirname, '../src/sfc')
}

很顯然,這裏 web 對應的真實的路徑是 path.resolve(__dirname, '../src/platforms/web'),這個路徑就找到了 Vue.js 源碼的 web 目錄。然後 resolve 函數通過 path.resolve(aliases[base], p.slice(base.length + 1)) 找到了最終路徑,它就是 Vue.js 源碼 web 目錄下的 entry-runtime.js。因此,web-runtime-cjs 配置對應的入口文件就找到了。它經過 Rollup 的構建打包後,最終會在 dist 目錄下生成 vue.runtime.common.js

  1. Runtime Only VS Runtime + Compiler,如下所示:
  • 通常我們利用 vue-cli 去初始化我們的 Vue.js 項目的時候會詢問我們用 Runtime Only 版本的還是 Runtime + Compiler 版本。下面我們來對比這兩個版本

    • Runtime Only。我們在使用 Runtime Only 版本的 Vue.js 的時候,通常需要藉助如 webpackvue-loader 工具把 .vue 文件編譯成 JavaScript,因爲是在編譯階段做的,所以它只包含運行時的 Vue.js 代碼,因此代碼體積也會更輕量。

    • Runtime + Compiler。我們如果沒有對代碼做預編譯,但又使用了 Vuetemplate 屬性並傳入一個字符串,則需要在客戶端編譯模板,如下所示:

    // 需要編譯器的版本
    new Vue({
      template: '<div>{{ hi }}</div>'
    })
    
    // 這種情況不需要
    new Vue({
      render (h) {
        return h('div', this.hi)
      }
    })
    

    因爲在 Vue.js 2.0 中,最終渲染都是通過 render 函數,如果寫 template 屬性,則需要編譯成 render 函數,那麼這個編譯過程會發生運行時,所以需要帶有編譯器的版本。很顯然,這個編譯過程對性能會有一定損耗,所以通常我們更推薦使用 Runtime-Only 的 Vue.js。

  1. 總結:可以瞭解到 Vue.js 的構建打包過程,也知道了不同作用和功能的 Vue.js 它們對應的入口以及最終編譯生成的 JS 文件。儘管在實際開發過程中我們會用 Runtime Only 版本開發比較多,但爲了分析 Vue 的編譯過程,重點分析的源碼是 Runtime + CompilerVue.js

四、入口開始

  1. 我們之前提到過 Vue.js 構建過程,在 web 應用下,我們來分析 Runtime + Compiler 構建出來的 Vue.js,它的入口是 src/platforms/web/entry-runtime-with-compiler.js
/* @flow */

import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'

import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

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) {
    let template = options.template
    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')
      }

      const { render, staticRenderFns } = compileToFunctions(template, {
        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)
}

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
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
  }
}

Vue.compile = compileToFunctions

export default Vue

那麼,當我們的代碼執行 import Vue from 'vue' 的時候,就是從這個入口執行代碼來初始化 Vue,
那麼 Vue 到底是什麼,它是怎麼初始化的。

  1. Vue 的入口,如下所示:
  • 在這個入口 JS 的上方我們可以找到 Vue 的來源:import Vue from './runtime/index',我們先來看一下這塊兒的實現,它定義在 src/platforms/web/runtime/index.js 中:
import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser, isChrome } from 'core/util/index'

import {
  query,
  mustUseProp,
  isReservedTag,
  isReservedAttr,
  getTagNamespace,
  isUnknownElement
} from 'web/util/index'

import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

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

// ...

export default Vue

這裏關鍵的代碼是 import Vue from 'core/index',之後的邏輯都是對 Vue 這個對象做一些擴展,可以先不用看,我們來看一下真正初始化 Vue 的地方,在 src/core/index.js 中:

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

這裏有 2 處關鍵的代碼,import Vue from './instance/index'initGlobalAPI(Vue),初始化全局 Vue API(我們稍後介紹),我們先來看第一部分,在 src/core/instance/index.js 中。

  1. Vue 的定義,如下所示:
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
  • 在這裏,我們終於看到了 Vue 它實際上就是一個用 Function 實現的類,我們只能通過 new Vue 去實例化它

  • 爲何 Vue 不用 ES6Class 去實現呢?我們往後看這裏有很多 xxxMixin的函數調用,並把 Vue 當參數傳入,它們的功能都是給 Vueprototype 上擴展一些方法,Vue 按功能把這些擴展分散到多個模塊中去實現,而不是在一個模塊裏實現所有,這種方式是用 Class 難以實現的。這麼做的好處是非常方便代碼的維護和管理,這種編程技巧也非常值得我們去學習

  1. initGlobalAPI,如下所示:
  • Vue.js 在整個初始化過程中,除了給它的原型 prototype 上擴展方法,還會給 Vue 這個對象本身擴展全局的靜態方法,它的定義在 src/core/global-api/index.js 中:
export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}
  • 這裏就是在 Vue 上擴展的一些全局方法的定義,Vue 官網中關於全局 API 都可以在這裏找到。有一點要注意的是,Vue.util 暴露的方法最好不要依賴,因爲它可能經常會發生變化,是不穩定的
  1. 總結:那麼 Vue 的初始化過程基本介紹完畢。Vue 它本質上就是一個用 Function 實現的 Class,然後它的原型 prototype 以及它本身都擴展了一系列的方法和屬性。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章