某課網有個488人名幣的源碼解讀視頻看不起,只能搜很多得資料慢慢理解,看源碼能知道大佬的功能模塊是怎么分塊寫的,怎么復用的,已經vue是怎么實現的
資料來自
vue源碼
喜歡唱歌的小獅子
web喵喵喵
Vue.js源碼全方位深入解析
恰恰虎的博客
learnVue
最后四集視頻
總文件目錄
- scripts:包含構建相關的腳本和配置文件。作者聲明一般開發不需要關注此目錄
- dist:構建出的不同分發版本,只有發布新版本時才會跟新,開發分支的新特性不會反映在此
- packages:包含服務端渲染和模板編譯器兩種不同的NPM包,是提供給不同使用場景使用的
- test:包含所有測試代碼
- flow:這是一個類型檢查工具,可以加入類型的限制,提高代碼質量,導致源碼代碼變成了這樣
function sum(a: number, b:number) {
return a + b;
}
- src:Vue的源碼,使用ES6和Flow類型注釋編寫的
- types:使用TypeScript定義的類型聲明,並且包含了測試文件,不太明白為什么要同時使用兩種靜態類型檢查語言
src文件夾
- Compiler 編譯器
- parser:解析器的作用是將模板轉換成元素AST對象。
- optimizer:優化器負責檢測靜態抽象樹的渲染進行優化。
- codegen:代碼生成器直接從AST對象生成代碼字符串。
- Core 核心
- Observer:觀察者系統,實現監測數據變化的功能。
- Vdom:Vue虛擬節點樹,實現虛擬節點的創建和刷新功能。
- instance:Vue類,包含構造函數和原型方法的創建。
- Global-API:全局API。
- Components:通用抽象組件。
- util:輔助函數。
- Platforms 平台,不同平台的區別代碼
- Server 服務器渲染,ssr
- Sfc 單文件組件文件編譯,這一文件夾目前只包含了一個叫parser.js的文件,用來將單文件組件解析為SFC描述對象,輸出給編譯器繼而執行模板編譯。
- Shared 共享常量和函數,是一個放工具的文件夾
Global-api
- util:雖然暴露了一些輔助方法,但官方並不將它們列入公共API中,不鼓勵外部使用。
- set:設置響應式對象的響應式屬性,強制觸發視圖更新,在數組更新中非常實用,不適用於根數據屬性。
- delete:刪除響應式屬性強制觸發視圖更新, 使用情境較少。
- nextTick:結束此輪循環后執行回調,常用於需要等待DOM更新或加載完成后執行的功能。
- use:安裝插件,自帶規避重復安裝。
- mixin:常用於混入插件功能,不推薦在應用代碼中使用。
- extend:創建基於Vue的子類並擴展初始內容。
- directive:注冊全局指令。
- component:注冊全局組件。
- filter:注冊全局過濾器。
Vue的核心
- 創建Vue的構造函數,
src/core/instance/index.js
function Vue (options) {
// 安全性判斷,如果不是生產環境且不是Vue的實例,在控制台輸出警告
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)
}
// 下面這些Mixin都是往Vue函數的原型對象里添加方法
// 掛載初始化方法
initMixin(Vue)
// 掛載狀態處理相關方法
stateMixin(Vue)
// 掛載事件響應相關方法
eventsMixin(Vue)
// 掛載生命周期相關方法
lifecycleMixin(Vue)
// 掛載視圖渲染方法
renderMixin(Vue)
// 這里就是暴露給Global-api去添加全局的方法
export default Vue
- initMixin,添加_init方法,里面有后續的所有重要的
export function initMixin (Vue: Class<Component>) {
// 在Vue類的原型上掛載_init()方法
// 接收類型為原始對象的options形參,此參數為非必選參數
Vue.prototype._init = function (options?: Object) {
// 將實例對象賦值給vm變量
// 這里會再次進行Component類型檢查確保vm接收到的是Vue類的實例
const vm: Component = this
// 合並options對象
if (options && options._isComponent) {
// 內部組件的options初始化
initInternalComponent(vm, options)
} else {
// 否則執行合並options函數,並賦值給vm的公共屬性
// 在這里的合並函數主要是解決與繼承自父類的配置對象的合並
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// 暴露實例對象
vm._self = vm
// 初始化實例的生命周期相關屬性
initLifecycle(vm)
// 初始化事件相關屬性和監聽功能
initEvents(vm)
// 初始化渲染相關屬性和功能
initRender(vm)
// 調用生命周期鈎子函數beforeCreate
callHook(vm, 'beforeCreate')
// 初始化父組件注入屬性
initInjections(vm) // resolve injections before data/props
// 初始化狀態相關屬性和功能
initState(vm)
// 初始化子組件屬性提供器
initProvide(vm) // resolve provide after data/props
// 調用生命周期鈎子函數created
callHook(vm, 'created')
// 執行DOM元素掛載函數
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
生命周期相關
- lifecycleMixin
// 導出lifecycleMixin函數,接收形參Vue,
// 使用Flow進行靜態類型檢查指定為Component類
export function lifecycleMixin (Vue: Class<Component>) {
// 為Vue原型對象掛載_update私有方法
// 接收vnode虛擬節點類型參數和一個可選的布爾值hydrating
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { ... }
// 為Vue實例掛載$forceUpdate方法,實現強制更新
Vue.prototype.$forceUpdate = function () { ... }
// 為Vue實例掛載$destroy方法
Vue.prototype.$destroy = function () { ... }
}
- initLifecycle,主要是在生命周期開始之前設置一些相關的屬性的初始值,組件也有自己的生命周期
// 導出initLifecycle函數,接受一個Component類型的vm參數
export function initLifecycle (vm: Component) {
// 獲取實例的$options屬性,賦值為options變量
const options = vm.$options
let parent = options.parent
// 判斷是否存在且非抽象
if (parent && !options.abstract) {
// 遍歷尋找最外層的非抽象父級
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
// 將實例添加到最外層非抽象父級的子組件中
parent.$children.push(vm)
}
// 初始化實例的公共屬性
// 設置父級屬性,如果之前的代碼未找到父級,則vm.$parent為undefined
vm.$parent = parent
// 設置根屬性,沒有父級則為實例對象自身
vm.$root = parent ? parent.$root : vm
// 初始化$children和$refs屬性
// vm.$children是子組件的數組集合
// vm.$refs是指定引用名稱的組件對象集合
vm.$children = []
vm.$refs = {}
// 生命周期相關的私有屬性
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
調用生命周期的方法callHook
export function callHook (vm: Component, hook: string) {
//記錄當前watch的實例
pushTarget()
//獲取相應鈎子的事件處理方法數組
const handlers = vm.$options[hook]
//執行對應的事件方法
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
try {
handlers[i].call(vm)
} catch (e) {
handleError(e, vm, `${hook} hook`)
}
}
}
//觸發@hook:定義的鈎子方法
if (vm._hasHookEvent) {
// 發布訂閱調用模式,$emit來自initEvent
vm.$emit('hook:' + hook)
}
//釋放當前的watch實例
popTarget()
}
函數相關
- eventsMixin,添加幾個用於訂閱發布的方法
export function eventsMixin (Vue: Class<Component>) {
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
// 定義實例變量
const vm: Component = this
// 如果傳入的event參數是數組,遍歷event數組,為所有事件注冊fn監聽函數
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[i], fn)
}
}
// 返回實例本身
return vm
}
// 為Vue原型對象掛載$once方法
// 參數event只接受字符串,fn是監聽函數
Vue.prototype.$once = function (event: string, fn: Function): Component { ... }
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { ... }
// 為Vue原型對象掛載$emit方法,只接受單一event
Vue.prototype.$emit = function (event: string): Component { ... }
}
- initEvents
// 定義並導出initEvents函數,接受Component類型的vm參數
export function initEvents (vm: Component) {
//初始化vm._events對象
// 存儲父組件綁定當前子組件的事件,保存到vm._events中
vm._events = Object.create(null);
//是否存在hook:鈎子事件
vm._hasHookEvent = false;
// init parent attached events
var listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
渲染相關
- renderMixin
export function renderMixin (Vue: Class<Component>) {
Vue.prototype.$nextTick = function (fn: Function) { ... }
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
vm.$vnode = _parentVnode
// render 就是 render表達式,
// vnode 就是 虛擬dom
let vnode = render.call(vm._renderProxy, vm.$createElement)
return vnode
}
}
- initRender,就是定義幾個屬性而已
export function initRender (vm: Component) {
// 初始化實例的根虛擬節點
vm._vnode = null
// 定義實例的靜態樹節點
vm._staticTrees = null
// 獲取配置對象
const options = vm.$options
// 設置父占位符節點
const parentVnode = vm.$vnode = options._parentVnode
// renderContext存儲父節點有無聲明上下文
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
/*將createElement函數綁定到該實例上,該vm存在閉包中,不可修改,vm實例則固定。這樣我們就可以得到正確的上下文渲染*/
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
/*常規方法被用於公共版本,被用來作為用戶界面的渲染方法*/
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners這個屬性進行數據監聽
const parentData = parentVnode && parentVnode.data
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
參數處理
- stateMixin
export function stateMixin (Vue: Class<Component>) {
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
}
- initState,這里才真正跟MVVM連接起來
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
// 這里展示下initData方法,其他也差不多
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 判斷props對象跟data對象跟methods對象里有沒有名字一樣的key值
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`
)
} else if (!isReserved(key)) {
// 代理data的值,當訪問this.message時,實際上訪問的是this[_data][message]
proxy(vm, `_data`, key)
}
}
// 對內容進行監聽
observe(data, true)
}
上面出現的幾個重要的方法
- proxy,用於代理參數,當訪問this.message時,實際上訪問的是this[_data][message]
- defineReactive,最核心的對象監聽方法
- observe和Observe,這兩個都是在內部調用了defineReactive
總結所有的init

當參數處理完畢,data,prop等數據已經被監聽,Dep數組也已經創建好,但是是空的,這時可以看到上面的init的生命周期才寫到created,我們知道在生命周期mounted前是沒有dom元素的,所以在$mount階段才開始生成虛擬dom,$mount方法在哪里定義的呢,查看下一篇筆記
