Vue
,如今前端的當紅炸子雞,隨着熱度指數上升,實在是有必要從源碼的角度,對它功能的實現原理一窺究竟。我的以爲看源碼主要是看兩樣東西,從宏觀上來講是它的設計思想和實現原理;微觀上來講就是編程技巧,也就是俗稱的騷操做。咱們此次的側重點是它的實現原理。好吧,讓咱們推開它那神祕的大門,進入Vue
的世界~html
vue
到底是什麼?爲何就能實現這麼多酷炫的功能,不知道你們有沒有思考過這個問題。其實在每次初始化vue
,使用new Vue({...})
時,不難發現vue
實際上是一個類。不過即便在ES6
已經如此普及的今天,vue
的定義倒是普通構造函數定義的,爲何沒有采用ES6
的class
呢?這個咱們稍後回答,經過層層追蹤終於找到了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)
複製代碼
如今能夠回答以前的問題了,爲何不採用ES6
的class
來定義,由於這樣能夠方便的把vue
的功能拆分到不一樣的目錄中去維護,將vue
的構造函數傳入到如下方法內:web
_init
方法。$set
,$delete
,$watch
方法。$on
,$once
,$off
,$emit
。_update
,及生命週期相關的$forceUpdate
和$destroy
。$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方法
}
複製代碼
這裏有部分API
和xxxMixin
定義的原型方法功能是相似或相同的,如this.$set
和Vue.set
他們都是使用set
這樣一個內部定義的方法。vue-cli
這裏須要提一下vue
的架構設計,它的架構是分層式的。最底層是一個ES5
的構造函數,再上層在原型上會定義一些_init
、$watch
、_render
等這樣的方法,再上層會在構造函數自身定義全局的一些API
,如set
、nextTick
、use
等(以上這些是不區分平臺的核心代碼),接着是跨平臺和服務端渲染(這些暫時不在討論範圍)及編譯器。將這些屬性方法都定義好了以後,最後會導出一個完整的構造函數給到用戶使用,而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
環境下,如web
、Node.js
、weex
嵌入原生應用中。
src/platforms:針對web
平臺和weex
平臺分別的實現,並提供統一的API
供調用。
src/observer:vue
檢測數據數據變化改變視圖的代碼實現。
src/vdom:將render
函數轉爲vnode
從而patch
爲真實dom
以及diff
算法的代碼實現。
dist:存放着針對不一樣使用方式的不一樣的vue
版本。
vue
使用的是rollup
構建的,具體怎麼構建的不重要,總之會構建出不少不一樣版本的vue
。按照使用方式的不一樣,能夠分爲如下三類:
<script>
標籤直接在瀏覽器中使用。webpack1
。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'
,推薦仍是使用運行時版本。好吧,具體區別最後咱們以一個面試時常常會被問到的問題做爲本章節的結束。
面試官微笑而又不失禮貌的問到:
runtime
和runtime-only
這兩個版本的區別?懟回去:
6kb
。loader
作的離線編譯,運行性能更高。順手點個贊或關注唄,找起來也方便~