Vue原理解析(一):Vue究竟是什麼?

Vue,如今前端的當紅炸子雞,隨着熱度指數上升,實在是有必要從源碼的角度,對它功能的實現原理一窺究竟。我的以爲看源碼主要是看兩樣東西,從宏觀上來講是它的設計思想和實現原理;微觀上來講就是編程技巧,也就是俗稱的騷操做。咱們此次的側重點是它的實現原理。好吧,讓咱們推開它那神祕的大門,進入Vue的世界~html

vue是什麼?

vue到底是什麼?爲何就能實現這麼多酷炫的功能,不知道你們有沒有思考過這個問題。其實在每次初始化vue,使用new Vue({...})時,不難發現vue實際上是一個類。不過即便在ES6已經如此普及的今天,vue的定義倒是普通構造函數定義的,爲何沒有采用ES6class呢?這個咱們稍後回答,經過層層追蹤終於找到了vue被定義的地方:前端

function Vue(options) {
  ...
  this._init(options)
}
複製代碼

由於是原理解析,flow的類型檢測及一些邊界狀況,如使用方式不對或參數不對或不是主要邏輯的代碼咱們就省略掉吧。好比省略號這裏邊界狀況是使用時必須是new Vue()的形式,不然會報錯。vue

其實vue源碼就像一棵樹,咱們看以前最好要肯定看什麼功能,而後避開那些分叉邏輯,咱們接下來的目標就是以new Vue()開始,走完一整條從初始化、數據、模板到真實Dom的這整個流程。java

這就是vue最初始被定義的地方,你沒看錯,就是這麼簡單。當執行new Vue時,內部會執行一個方法 this._init(options),將初始化的參數傳入。node

這裏須要說明一點,在vue的內部,_符號開頭定義的變量是供內部私有使用的,而$ 符號定義的變量是供用戶使用的,並且用戶自定義的變量不能以_$開頭,以防止內部衝突。咱們接着看:webpack

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

function Vue(options) {
  ...
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
複製代碼

如今能夠回答以前的問題了,爲何不採用ES6class來定義,由於這樣能夠方便的把vue的功能拆分到不一樣的目錄中去維護,將vue的構造函數傳入到如下方法內:web

  • initMixin(Vue):定義_init方法。
  • stateMixin(Vue):定義數據相關的方法$set,$delete,$watch方法。
  • eventsMixin(Vue):定義事件相關的方法$on$once$off$emit
  • lifecycleMixin(Vue):定義_update,及生命週期相關的$forceUpdate$destroy
  • renderMixin(Vue):定義$nextTick_render將render函數轉爲vnode

這些方法都是在各自的文件內維護的,從而讓代碼結構更加清晰易懂可維護。如this._init方法被定義在:面試

export function initMixin(Vue) {
  Vue.prototype._init = function(options) {
    ...當執行new Vue時,進行一系列初始化並掛載
  }
}
複製代碼

再這些xxxMixin完成後,接着會定義一些全局的API算法

export function initGlobalAPI(Vue) {
  Vue.set方法
  Vue.delete方法
  Vue.nextTick方法
  
  ...
  
  內置組件:
  keep-alive
  transition
  transition-group
  
  ...
  
  initUse(Vue):Vue.use方法
  initMixin(Vue):Vue.mixin方法
  initExtend(Vue):Vue.extend方法
  initAssetRegisters(Vue):Vue.component,Vue.directive,Vue.filter方法
}
複製代碼

這裏有部分APIxxxMixin定義的原型方法功能是相似或相同的,如this.$setVue.set他們都是使用set這樣一個內部定義的方法。vue-cli

這裏須要提一下vue的架構設計,它的架構是分層式的。最底層是一個ES5的構造函數,再上層在原型上會定義一些_init$watch_render等這樣的方法,再上層會在構造函數自身定義全局的一些API,如setnextTickuse等(以上這些是不區分平臺的核心代碼),接着是跨平臺和服務端渲染(這些暫時不在討論範圍)及編譯器。將這些屬性方法都定義好了以後,最後會導出一個完整的構造函數給到用戶使用,而new Vue就是啓動的鑰匙。這就是咱們陌生且又熟悉的vue,至於Vue.prototype._init內部作了啥?咱們下章節再說吧,由於還有不少其餘的要補充。

目錄結構

剛纔是從比較微觀的角度近距離的觀察了vue,如今咱們從宏觀角度來了解它內部的代碼結構是如何組建起來的。 目錄以下:

|-- dist  打包後的vue版本
|-- flow  類型檢測,3.0換了typeScript
|-- script  構建不一樣版本vue的相關配置
|-- src  源碼
    |-- compiler  編譯器
    |-- core  不區分平臺的核心代碼
        |-- components  通用的抽象組件
        |-- global-api  全局API
        |-- instance  實例的構造函數和原型方法
        |-- observer  數據響應式
        |-- util  經常使用的工具方法
        |-- vdom  虛擬dom相關
    |-- platforms  不一樣平臺不一樣實現
    |-- server  服務端渲染
    |-- sfc  .vue單文件組件解析
    |-- shared  全局通用工具方法
|-- test 測試
複製代碼
  • flow:javaScript是弱類型語言,使用flow以定義類型和檢測類型,增長代碼的健壯性。

  • src/compiler:將template模板編譯爲render函數。

  • src/core:與平臺無關通用的邏輯,能夠運行在任何javaScript環境下,如webNode.jsweex嵌入原生應用中。

  • src/platforms:針對web平臺和weex平臺分別的實現,並提供統一的API供調用。

  • src/observer:vue檢測數據數據變化改變視圖的代碼實現。

  • src/vdom:將render函數轉爲vnode從而patch爲真實dom以及diff算法的代碼實現。

  • dist:存放着針對不一樣使用方式的不一樣的vue版本。

vue版本

vue使用的是rollup構建的,具體怎麼構建的不重要,總之會構建出不少不一樣版本的vue。按照使用方式的不一樣,能夠分爲如下三類:

  • UMD:經過<script>標籤直接在瀏覽器中使用。
  • CommonJS:使用比較舊的打包工具使用,如webpack1
  • ES Module:配合現代打包工具使用,如webpack2及以上。

而每一個使用方式內又分爲了完整版和運行時版本,這裏主要以ES Module爲例,有了官方腳手架其餘兩類應該沒多少人用了。再說明這兩個版本的區別以前,抱歉我又要補充點其餘的。在vue的內部是隻認render函數的,咱們來本身定義一個render函數,也就是這麼個東西:

new Vue({
  data: {
    msg: 'hello Vue!'
  },
  render(h) {
    return h('span', this.msg);
  }
}).$mount('#app');
複製代碼

可能有人會納悶了,既然只認render函數,同時咱們開發好像歷來並無寫過render函數,而是使用的template模板。這是由於有vue-loader,它會將咱們在template內定義的內容編譯爲render函數,而這個編譯就是區分完整版和運行時版本的關鍵所在,完整版就自帶這個編譯器,而運行時版本就沒有,以下面這段代碼若是是在運行時版本環境下就會報錯了:

new Vue({
  data: {
    msg: 'hello Vue!'  
  },
  template: `<div>{{msg}}</div>`
})
複製代碼

vue-cli默認是使用運行時版本的,更改或覆蓋腳手架內的默認配置,將其更改成完整版便可經過編譯:'vue$': 'vue/dist/vue.esm.js',推薦仍是使用運行時版本。好吧,具體區別最後咱們以一個面試時常常會被問到的問題做爲本章節的結束。

面試官微笑而又不失禮貌的問到:

  • 請問runtimeruntime-only這兩個版本的區別?

懟回去:

  • 主要是兩點不一樣:
  1. 最明顯的就是大小的區別,帶編譯器會比不帶的版本大6kb
  2. 編譯的時機不一樣,編譯器是運行時編譯,性能會有必定的損耗;運行時版本是藉助loader作的離線編譯,運行性能更高。

下一篇:Vue原理解析(二):快速搞懂new Vue()時到底作了什麼?(上)

順手點個贊或關注唄,找起來也方便~

參考:

Vue.js源碼全方位深刻解析

Vue.js深刻淺出

Vue.js組件精講

剖析 Vue.js 內部運行機制

相關文章
相關標籤/搜索