前些時間大概看了一下Vue的官方文檔,也大概了解過 Vue 實現(xiàn)數(shù)據(jù)綁定的原理,但是還是想再深入得了解其具體的代碼細節(jié),便抽了些時間來閱讀源碼??戳藗€大概,另外自己水平有限,也只能把自己看懂的分享給大家。
Vue 的源碼用了flow進行類型檢測等工作,真正生產(chǎn)環(huán)境的代碼是 flow編譯而成的,所以在IDE中閱讀代碼時會出現(xiàn)很多代碼錯誤的提示,如果只需要閱讀而不對源碼進行修改和單元測試,則不必過多考慮。需要的話可以去閱讀一下flow這個工具的用法, 這里是官網(wǎng): https://flow.org/
另外,源碼中的語法大都是 ES6 的標準,還運用了很多Object.defineProperty來配置對象成員的 get 和 set 鉤子。
構(gòu)造過程 new Vue({...})
Vue的構(gòu)造器在 src/core/instance/index.js 中,但是在加載Vue這個API的時會先對它進行初始化,代碼位置在 src/core/index.js 中
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
Vue.version = '__VERSION__'
export default Vue
可以看到這里在 Vue 構(gòu)造器 被導(dǎo)出之前運行了 initGlobalAPI() 來擴展Vue 構(gòu)造器本身的屬性及方法(javascript 作為一個基于原型的 OO 語言, 其中一個函數(shù)也是一個對象,因此是可以有自己的方法和屬性的)。
initGlobalAPI() 在 src/core/global-api/index.js 中
再看 Vue 構(gòu)造器
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
在構(gòu)造器被運行之前,依然運行了一些函數(shù)來初始化Vue 原型的方法,這里很重要,但是在第一篇文章里就不詳述了。
在構(gòu)造 vue 對象時,運行了 _init(options) 函數(shù)來初始化,在src/core/instance/init.js 中
function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
......
// merge options
// 在之前對構(gòu)造器本身及其原型進行了擴展,這里進行合并處理
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
......
vm._self = vm
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件及時間處理
initRender(vm) // 初始化render
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 初始化 data, 的監(jiān)視等
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
......
if (vm.$options.el) {
vm.$mount(vm.$options.el) // 掛載
}
}
}
在掛載之前先要對生命周期,模板渲染等進行初始化,比較重要的是建立對數(shù)據(jù)的監(jiān)視來實現(xiàn)動態(tài)綁定以在數(shù)據(jù)更改時重新渲染VDOM 并更新DOM。
掛載時要進行模板的編譯和DOM的生成,這一步之后,就順利構(gòu)造完成了。
VDOM
Vue 同 react 一樣, 運用了自己的虛擬DOM來實現(xiàn)DOM的更新,大體可以理解為在數(shù)據(jù)發(fā)生變化時先建立一個虛擬的DOM,并且與之前的虛擬DOM進行比較,最后經(jīng)過diff 之后找到不同并通過 patch 來更行真正的DOM。其優(yōu)點在于避免了大量的DOM操作(因為DOM操作很費資源)。
這里不進行詳細分析,但是推薦給大家一篇文章,里面介紹了諸多框架中數(shù)據(jù)監(jiān)測的實現(xiàn)
http://teropa.info/blog/2015/03/02/change-and-its-detection-in-javascript-frameworks.html
模板編譯
Vue 的模板編譯大體需要經(jīng)過一下幾個步驟
- 由template編譯成 ast render 函數(shù)(AST是指抽象語法樹,具體就不說了,因為我也不會)
- 運行render 函數(shù),編譯成 vnode(虛擬DOM節(jié)點)
- 通過patch更行DOM
而具體的模板編譯和patch都是相對比較復(fù)雜的算法,這里就先不細說(以后可能也細說不了,得看我個人造化了)
數(shù)據(jù)監(jiān)視(數(shù)據(jù)綁定)
Vue 的數(shù)據(jù)綁定主要是通過get 和 set 鉤子來實現(xiàn)的,而鉤子函數(shù)究竟干了什么呢,其實在初始化的過程中建立了一系列的 Observer 對象來監(jiān)視數(shù)據(jù)的變化,在 Observer 中又有 Watcher 對象,負責監(jiān)視到數(shù)據(jù)變化之后的操作(不知道說的對不對,個人理解吧)。Observer 的建立過程中就設(shè)置了諸多get set 鉤子。Watcher則是在掛載時建立(也有可能在其他地方也建立了但是還沒有讀到)。
結(jié)語
以上就是對Vue 2.5 源碼的概述,由于個人水平有限,可能有很多錯誤,希望大家能提出,共同學習。