Why?
網上現有的Vue源碼解析文章一搜一大批,但是為什么我還要去做這樣的事情呢?因為覺得紙上得來終覺淺,絕知此事要躬行
。
然后平時的項目也主要是Vue,在使用Vue的過程中,也對其一些約定產生了一些疑問,可能官網上只會建議你這么做,但是核心實現我們可能並不知道。比如:
- v-for key 是如何達到“就地復用”策略
- 數組更新檢測是如何完成的
- set 為什么就能動態添加根級別的響應式屬性
- 為什么Vue可以跨平台支持weex,以及后來出現的mpvue
- ...
其次,很久沒有更新內容了,之前對Vue源碼也是有點研究,只不過沒有很體系的記錄,現在抽了點時間,做了一次基礎的總結吧。一方面是因為想要克服自己的惰性,另一方面也是想重新溫故一遍。
What?
一共分成了10個基礎部分,后續還會繼續記錄。我們可以先看一下概覽:
然后我們來看一下基礎的目錄:
入口開始,解讀Vue源碼(三)—— initMixin 上篇
入口開始,解讀Vue源碼(三)—— initMixin 下篇
入口開始,解讀Vue源碼(四)—— 實現一個基礎的 Vue 雙向綁定
入口開始,解讀Vue源碼(六)—— $mount 內部實現 --- compile parse函數生成AST
入口開始,解讀Vue源碼(七)—— $mount 內部實現 --- compile optimize標記節點
入口開始,解讀Vue源碼(八)—— $mount 內部實現 --- compile generate 生成render函數
入口開始,解讀Vue源碼(九)—— $mount 內部實現 --- render函數 --> VNode
入口開始,解讀Vue源碼(十)—— $mount 內部實現 --- patch
開篇:入口開始,解讀Vue源碼(一)-- 造物創世
世間萬物的起源來自於盤古的開天辟地,Vue 項目的起源,源於一次Vue的實例化:
new Vue({
el: ...,
data: ...,
....
})
那么在這次實例化的過程中,究竟發生了哪些行為?讓我們來一探究竟。打開Vue的源碼文件,其核心代碼在src/core
目錄下。下面我們從入口文件index.js
開始進入:(剛開始看的時候,我們可能不太清楚每個引用方法的具體實現,不過沒關系,我們可以自己根據他的命名來YY一下。)
// src/core/index.js
// 這里應該是我們 Vue 核心方法
import Vue from './instance/index'
// 根據命名,應該可以猜出這里是初始化一些全局API
import { initGlobalAPI } from './global-api/index'
// 根據命名,這里應該是獲取一個Boolean類型的變量,來判斷是不是ssr
import { isServerRendering } from 'core/util/env'
// 這里開始執行初始化全局變量
initGlobalAPI(Vue)
// 為Vue原型定義屬性$isServer
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
// 為Vue原型定義屬性$ssrContext
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
Vue.version = '__VERSION__'
export default Vue
下面我們來一步步驗證我們的猜測,首先找到core/instance/index
文件,可以清晰的看到:
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 Class,然后又調用了一系列init、mixin
這樣的方法來初始化一些功能,具體的我們后面在分析,不過通過代碼我們可以確認的是:沒錯!這里確實是導出了一個 Vue 功能類。
接下來,我們接着看initGlobalAPI
這個東西,其實在Vue官網上,就已經為我們說明了Vue的全局屬性:
那我們來看看,是不是這么回事(內容太多,只貼一下主要的代碼):
// core/global-api/index
...
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)
// 這些工具方法不視作全局API的一部分,除非你已經意識到某些風險,否則不要去依賴他們
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)
})
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
// 定義全局方法
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
-
【Vue.config】 各種全局配置項
-
【Vue.util】 各種工具函數,還有一些兼容性的標志位(哇,不用自己判斷瀏覽器了,Vue已經判斷好了)
-
【Vue.set/delete】 這個你文檔應該見過
-
【Vue.nextTick】
-
【Vue.options】 這個options和我們上面用來構造實例的options不一樣。這個是Vue默認提供的資源(組件指令過濾器)。
-
【Vue.use】 通過initUse方法定義
-
【Vue.mixin】 通過initMixin方法定義
-
【Vue.extend】通過initExtend方法定義
接下來便是提供給ssr使用的全局變量$isServer
和 $ssrContext
。 關於他們的使用,其實ssr文檔也有說明:Head 管理
到這里,我們的入口文件差不多就了解清楚了,接下來,我們開始去了解一下 Vue class 的具體實現,其中我們會了解到Vue的相關生命周期的知識。
End?
文章前后也是利用碎片時間總結整理而成,有些也是翻閱了很多的資料,也有過引用巨人的段落,文章中有所標注。如果沒有標注,可能是本人忘記了,歡迎提醒。文章中如果有筆誤或者不正確的解釋,也歡迎批評指正,共同進步。
最后: