--本文采自本人公衆號【猴哥別瞎說】vue
接着前文的準備。咱們知道:在 Vue2.6.10 的源碼結構中,入口文件是在 src/platforms/web/entry-runtime-with-compiler.js。那麼咱們就具體來看看裏面的代碼吧。node
在初次看源碼的時候,有兩個值得注意的點:web
- 不要摳細節,把握總體的方向,以囫圇吞棗的方式看便可。
- 制定閱讀的目標,有重點地去看
。瀏覽器
咱們先來看看咱們這次的目標:Vue的構造函數到底在哪裏?具體的初始化過程又是怎樣的?bash
咱們先來看 entry-runtime-with-compiler.js 文件的核心部分:併發
//將$mount函數進行拓展,對用戶輸入的$options的template/el/render進行解析、處理
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
//處理用戶自定義選項,可知執行順序是 render > template > el
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
//字符串模板
if (typeof template === 'string') {
//選擇器
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
//傳進來的是dom
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
//若是隻設置el,那麼就會直接獲取其中的元素
//值得注意的是,會將元素自己覆蓋
template = getOuterHTML(el)
}
if (template) {
//獲取render函數
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
}
}
//執行原先的掛載函數
return mount.call(this, el, hydrating)
}
複製代碼
看代碼知道,這個文件僅僅是對已有的 mount 函數作了加強:將用戶輸入的自定義模板選項,進行處理併發揮render函數。dom
從這裏能夠得出的結論是:ide
1.若是參數中同時存在el、render、template變量,其優先級順序爲:render > template > el。函數
2.編譯發生的時間段(compileToFunctions,將模板轉化爲render函數)在整個初始化過程當中很是早的,在執行具體mount函數以前。oop
這裏並非真正的 Vue 構造函數所在。咱們接着看 ./runtime/index.js 文件:
// install platform patch function
//定義補丁函數,這是將虛擬DOM轉換爲真實DOM的操做,很是重要
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
//定義掛載函數
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
//這纔是真正的掛載函數
return mountComponent(this, el, hydrating)
}
複製代碼
結果發現,該文件僅僅是定義了原型鏈上的__patch__
函數以及$mount
函數。而詳細的掛載過程還不在這裏。具體細節咱們先不看。
繼續找:core/index.js
//初始化全局API
initGlobalAPI(Vue)
複製代碼
結果依然仍是失望的。不由感慨:這個俄羅斯套娃也太多層了吧。在core/index.js內部,也僅僅只是初始化了全局API而已。這個並非今天咱們關注的重點,仍是繼續找:src/core/instance/index.js :
function Vue (options) {
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
複製代碼
在這裏,終於找到了Vue的構造函數!!!與此同時,咱們也發現:該文件執行了許多 Mixin 操做。這些詳細的部分咱們先不理會,咱們先來看看構造函數中的 this._init() 到底作了什麼事情呢?
能夠經過瀏覽器調試的方式,也能夠經過全局搜索prototype._init
的方式,找到_init()
的所在文件:src/core/instance/init.js。
//初始化順序:生命週期->事件監聽->渲染->beforeCreate->注入->state初始化->provide->created
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 初始化 props/data/watch/methods, 此處會是研究數據響應化的重點
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
//若是存在el元素,則會自動執行$mount,這也是必需要理解的
//也就是說,在寫法上若是有el元素,能夠省略$mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
複製代碼
從這裏能夠得出的結論是:
1.在
_init()
函數中,咱們看到vue實例初始化時候的執行順序:生命週期->事件監聽->渲染->beforeCreate->注入數據inject->組件狀態初始化->提供數據provide->created。2.若是存在el元素,則會自動執行掛載。
若是咱們想要了解數據響應化的細節,那就應該詳細去看initState()函數。它會是咱們下節課的重點。
在這個時候,咱們找到了主要的文件脈絡。至於具體的初始化過程,咱們還須要深刻去看$mount過程當中作了什麼。因而咱們回到真正的掛載函數mountComponent()函數裏面去看看,其中發生的掛載細節:
//這纔是真正的mount函數
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
...
callHook(vm, 'beforeMount')
//核心代碼邏輯
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
...
} else {
updateComponent = () => {
//更新Component,主要作了兩個事情:render(生成vdom)、update(轉換vdom爲dom)
vm._update(vm._render(), hydrating)
}
}
// 在此處定義Watcher(一個Vue實例對應的是一個Watcher),而且與updateComponent關聯起來
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
複製代碼
在上面的代碼中,咱們看到,在vue實例掛載的過程當中,會新建一個 Watcher。這個 Watcher 的做用是相似於一個觀察者,它若是收到數據發生了變化的消息,那麼就會執行 updateComponent 函數。而這個 updateComponent 函數,主要作了兩個事情:render(生成 vdom)、update(轉換 vdom 爲 dom)。
綜上,梳理爲如下的流程圖:
那麼,Watcher 的工做原理是怎樣的,它是如何在整個數據響應化的過程當中發揮做用的?下篇文章將會給你答案!