本文將結合例子進行一步步講解,例子也會從簡單到複雜逐步提高,這樣理解的更深入html
<div id="app"></div>
複製代碼
const app = new Vue({
template: '<div>child</div>',
})
app.$mount('#app');
複製代碼
首先先調用new Vue
建立了一個實例,在core/instance/index
中定義了Vue
的構造函數node
function Vue(options) {
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
複製代碼
在該文件中定義了Vue
構造函數,而且經過下面幾個Mixin
方法,在Vue
原型上也定義了一些方法,爲何不用class
由於class
沒有prototype
這麼靈活。web
Mixin 方法 |
方法 | 屬性 |
---|---|---|
initMixin |
_init |
- |
stateMixin |
$set 、$delete 、$watch |
$data 、$props |
eventsMixin |
$on 、$off 、$once 、$emit |
- |
lifecycleMixin |
_update 、$forceUpdate 、$detory |
- |
renderMixin |
_render 、$nextTick |
- |
_init
方法方法在core/instance/init.js
中,api
let uid = 0;
Vue.prototype._init = function (options) {
const vm = this;
vm._uid = uid++;
vm._isVue = true;
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm;
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callhook(vm, 'beforeCreate');
initInjections(vm);
initState(vm);
initProvide(vm),
callHook(vm, 'created')
}
複製代碼
屬性:app
Vue
的實例options
$children
中加入本身child
進行和父組件事件通訊的初始化,並在vm._events
對應的事件名稱加入這個函數<div class="parent">
<child @change="changeToDo"></child>
</div>
複製代碼
_c
、$createElement
。在Vue
原型上添加屬性$attrs
、$listeners
,並讓這些屬性進行響應式監聽inject
,inject
可以向子孫後代注入一個依賴,無論組件層次有多深props
、methods
、data
、computed
、watch
。讓數據響應式就是這個階段完成的,watch
和computed
都會生成對應的Watcher
provide
,用於接受inject
傳入的數據$mount
建立實例後,會調用_init
進行一系列的初始化操做,而後調用$mount
,$mount
在不一樣平臺有不一樣的定義,以web
爲例ide
Vue.$prototype.$mount = function (el) {
el = el && query(el);
const options = this.$options;
if (!options.render) {
let template = options.template
}
if (template) {
const { render, staticRenderFns } = compileToFunction(template, {
// ...
})
options.render = render;
options.staticRenderFns = staticRenderFns
}
return mount.call(this, el);
}
複製代碼
在不一樣的平臺調用不一樣的編譯方式最後把template
編譯爲render
函數。而後返回調用了mount
函數,最終調用的是mountComponent
函數
// cores/instance/lifecycle.js
function mountComponent(vm, el) {
vm.$el = el;
callHook(vm, 'beforeMount');
updateComponent = () => {
vm._update(vm._render())
}
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted && !vm._isDestoryed) {
callHook(vm, 'beforeUpdate')
}
}
})
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted')
}
}
複製代碼
首先聲明瞭回調函數upateComponent
,而後建立了渲染watcher
,渲染watcher
在初始化的時候就會執行回調函數updateComponent
,updateComponent
內部調用了_render
和_update
。這兩個方法在文章開頭的renderMixin
、lifecycleMixin
中定義了,_render
用於生成vnode
,_update
調用patch
:具體的path
可參照這篇文章Vue 源碼patch過程詳解,把vnode
中定義的內容渲染到真實DOM
中,最後調用mounted
鉤子。oop
data
把上面的例子進行更改,當template
中data
發生了更改,再看看具體的變化。post
new Vue({
template: '<div class="parent" @click="change">{{visible}}</div>'
data: {
return {
visible: 'all'
}
},
methods: {
change() {
this.visible = 'change';
}
}
})
複製代碼
當咱們點擊元素的時候,就會觸發change
事件更改data
中定義的值visible
學習
在initState
中會對data
中定義的值進行響應式設置
//core/instance/state.js
function initData(vm) {
let data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
observe(data, true)
}
複製代碼
這裏在初始化data
的時候,首先調用了自己,獲得返回的值,而後調用observe
進行數據響應式具體的可參照這篇文章深刻源碼學習Vue響應式原理。回到mountComponent
中,在建立renderWatcher
的時候首先會執行一遍updateComponent
,進行依賴收集
當數據更新後,依賴該data
數據的watcher
就會更新,這裏只有renderWatcher
有依賴,因此這個watcher
就會調用回調函數,從新執行一遍_render
和_update
。vm._render
根據template
生成的render
來生成vnode
// core/instance/render.js
Vue.prototype._render = function {
const vm = this;
const { render, _parentVnode } = vm.$options;
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots,
)
}
vm.$vnode = _parentVnode;
let vm.$vnode = _parentVnode
let vnode
try {
currentRenderingInstance = vm;
vnode = render.call(vm._renderProxy, vm.$createElement)
} finally {
currentRenderingInstance = null
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
vnode.parent = _parentVnode
return vnode;
}
複製代碼
能夠看到經過調用render
函數最後生成了vnode
, $createElement
也在當前文件夾中定義過,最後生成vnode
而後調用_update
執行patch
操做,把修改後的數據反映到真實DOM
對上面的例子在進行擴展,建立一個子組件
Vue.component('child', {
template: '<div class="child">child</div>'
})
new Vue({
template: '<div class="parent"><child></child></div>'
})
複製代碼
這裏聲明瞭一個子組件,而且父組件中調用了這個子組件,首先compileToFunctions
將其編譯爲對應的render
函數上面把new Vue
中聲明的template
編譯爲以下的render
函數
ƒ anonymous(
) {
with(this){return _c('div',{staticClass:"parent"},[_c('child')],1)}
}
複製代碼
當執行當前render
就會生成以下的vnode
當執行patch
的時候,當發現vnode
下面有children
就會對children
進行一系列操做。
Vue.component
回到Vue.component
聲明子組件,當調用Vue.component
都發生了什麼,方法定義在core/global-api/assets.js
中
Vue.component = function (id, definition) {
definition.name = definition.name || id;
definition = this.options._base.extend(definition)
this.options[type + 's'][id] = definition;
}
複製代碼
this._options._base
就是Vue
構造函數,至關於調用的是Vue.extend
,而後生成的definition
掛載到this.options.components
上,屬性名爲child
。Vue.extend
的方法定義在
Vue.extend = function (extendOptions) {
const Super = this;
const Sub = function VueComponent(options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
// ...
return Sub;
}
複製代碼
能夠看到返回是一個繼承Vue
的構造函數,而且建立實例的實例也會調用Vue
的_init
函數
patch
具體的邏輯能夠參照Vue 源碼patch過程詳解 回到父組件的$mount
操做,當建立渲染watcher
的時候,會當即執行updateComponent
,而後內部會執行_update
函數,能夠執行patch
操做,而後上面圖片能夠看到children
中存在值,就會走到createChildren
爲children
中的元素調用createElm
。由於child
是子組件就會走到 createComponent
而且二返回true
,在內部調用鉤子init
,init
鉤子函數具體實現以下:
const componentVNodeHooks = {
init: (vnode) => {
const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)
child.$mount(vnode.elm)
}
}
複製代碼
這裏就會調用createComponentInstanceForVnode
函數,這個函數實際調用的就是前面在Vue.extend
中返回的繼承於Vue
的構造函數,最後在調用$mount
函數。因此父子組件在渲染的時候鉤子執行的前後順序就是 父beforeMounted
=> 子beforeMounted
=> 子mounted
=> 父mounted