new Vue() 實例的初始化
Vue.js 是由 原型鏈 寫法來實現的庫,其構造函數在 src/core/instance/index.js
function Vue(options) {
if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) {
warn(...)
// 必須是以 new Vue 方式來創建實例
}
this._init(options)
}
initMixin(Vue) // 定義了 _init 方法
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue) // 定義了 _update 方法,$destroy 方法
renderMixin(Vue) // 定義了 _render 方法
function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this // 存儲當前實例
...
vm._isVue = true // 確認自身為Vue實例
if (options && options._isComponent) {
initInternalComponent(vm, options) // 對於組件傳入的options的合並
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
) // mergeOptions 將傳入的options和構造函數的options合並到實例本身的$options
}
... // 一些實例自身的初始化(包括beforeCreate/created鈎子的觸發)
if (vm.$options.el) {
// 如果合並之后的選項中有 el,則將其進行掛載
vm.$mount(vm.$options.el)
}
}
}
在附帶 compiler(編譯器)的版本中,$mount 的實現方式如下
位置:src/platform/web/entry-runtime/with-compiler.js
const mount = Vue.prototype.$mount
// hydrating == false
Vue.prototype.$mount = function (el, hydrating) {
const options = this.$options
if (!options.render) {
// 傳入的options沒有render函數,就通過template或者el,編譯成render函數並賦值給 options.render
}
return mount.call(this, el, hydrating) // 調用原來的 $mount 方法, return vm
}
// src/platform/web/runtime/index.js 原$mount方法
Vue.prototype.$mount = function(el, hydrating) {
el = el && inBrowser ? query(el) : undefined // 通過傳入的 el 來選擇對應的容器元素
return mountComponent(this, el) // return vm
}
// src/core/instance/lifecycle.js (經簡化)
function mountComponent(vm, el) {
vm.$el = el // 緩存 el
...
callHook(vm, 'beoforeMount')
let updateComponent = () => {
vm._update(vm._render())
}
// 創建 vm 對應的一個 渲染Watcher
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* 渲染watcher標志 */)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
創建 渲染Watcher 的過程
// src/core/observer/watcher.js
class Watcher {
constructor(vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm // Vue 實例
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
...
if (typeof expOrFn === 'function') {
this.getter = expOrFn // this.getter 其實就是定義Water時候傳入的 updateComponent
} else {
...
}
this.value = this.lazy ? undefined : this.get() // this.lazy == false
// 所以在賦值 this.value 的過程中,this.get()執行過程中,傳入的 getter 函數執行了一次
}
get() {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
...
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
}
分析 updateComponent 函數
// 定義
let updateComponent = () => {
vm._update(vm._render()) // 參數為 vnode
}
// 先分析 _render()
// src/core/instance/render.js
function renderMixin(Vue) {
Vue.prototype._render = function () {
const vm = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
...
// 設置 vm 的插槽
}
vm.$vnode = _parentVnode // 存儲 父vnode 節點
let vnode
try {
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
// vm._renderProxy.render(vm.$createElement)
// vm._renderProxy == vm
} catch (e) {}
...
vnode.parent = _parentVnode
return vnode
// 返回一個由 編譯/自帶的 render 函數執行得到的 vnode
}
}
// render 函數的調用其實就是 $createElement 的執行
// 同一個文件中有這樣的定義
// initRender 函數中
// 在 _init 方法初始化過程中,會調用initRender(vm)
vm.$createElement = (a,b,c,d) => createElement(vm,a,b,c,d,true)
// createElement 定義在 src/core/vdom/create-element.js
// src/core/vdom/create-element.js
function createElement(vm, tag, data, children, normalizationType, alwaysNormalize) {
if (Array.isArray(data) || isPrimitive(data)) {
// 參數重載
normalizationType = children
children = data
data = undefined
}
return _createElement(context, tag, data, normalizationType) // vnode
}
function _createElement(context, tag, data, children, normalizationType) {
...
// 將children 拍平成一維數組
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
// 適用於傳入一個由 vue-loader 編譯生成或者按格式編寫 tag
if (config.isReservedTag(tag)) {
// 平台原有的 tag 標簽,如H5的div等
vnode = new VNode(
config.parsePlatform(tag), data, children,
undefined, undefined, context
)
} else if (
(!data || !data.pre) &&
isDef(Ctor = resolveAssest(context.$options, 'components' ,tag))
) {
// 創建組件的vnode
vnode = createComponent(Ctor, data, context, children, tag) // return vnode
}
} else {
// 適用於直接傳入一個導出的 .vue 文件到render函數
// e.g. render: h => { return h(App) }
// 此處 h 相當於 createElement
// tag == App
vnode = createComponent(tag, data, context, children) // return vnode
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
...
return vnode
} else {
return createEmptyVNode() // 空白vnode
}
}
// 因此 vm._render() 函數會生成 vm 對應的 vnode 並返回給 vm._update 函數
// 分析 createComponent 函數
// src/core/vdom/create-component.js
function createComponent (Ctor, data, context, children, tag) {
if (isUndef(Ctor)) { return } // 沒有傳入構造函數/信息
const baseCtor = context.$options._base // 其實就是Vue,在合並選項的時候會合並進去
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor) // 傳入的構造信息通過 Vue.extend 轉為構造函數
}
...
installComponentHooks(data)
// 安裝組件鈎子,合並到data對象
/*
data = {
on: {...},
hook: {...}
}
*/
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.c_id}${name ? `-${name}` : ''}`,
data ,undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children }, // 該對象為componentOptions參數
asyncFactory
)
return vnode
}
再來看看 vm._update
// src/core/instance/lifecycle.js
function lifecycleMixin(Vue) {
Vue.prototype._update = function (vnode, hydrating) { // hydratng == false
const vm = this
const prevEl = vm.$el // 在 mountComponent 函數中緩存
const prevVnode = vm._vnode
vm._vnode = vnode // 緩存當前vnode 到實例的 _vnode屬性
const restoreActiveInstance = setActiveInstance(vm)
// 存儲當前的 vm 實例到 activeInstance
// 組件實際插入到 DOM 對象是在 __patch__ 過程中
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance() // 重新釋放當前 vm 實例,activeInstance 回退到上一個 vm 實例
...
// 根組件直接將 $el 更新到 父實例 的$el
// e.g. render: h => h(App) 最后會將 App 實例 patch 得到的 $el 更新到根實例(new Vue) 上
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
}
// 至此實例的渲染 watcher 創建完成,實例掛載結束
下面是組件如何 實際插入到DOM對象 的過程分析,在 patch 函數中,
// src/platforms/web/runtime/index.js
Vue.prototype.__patch__ = patch
// 這個patch 函數就是實際上調用的函數
// 經分析最后 patch 函數在 src/core/vdom/patch.js 的 createPatchFunction 中返回
// 簡化版 patch
function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
// 只有傳入了舊節點的信息
if (isDef(oldVnode)) invokeDestroy(oldVnode) // 銷毀舊節點
}
let isInitialPatch = false // 是否為根節點
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
isInitialPatch = true
crateElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
// h(App)是傳入的 oldVnode 是 el
if (!isRealElement && sameVndoe(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
if (isRealElement) {
...
oldVnode = emptyNodeAt(oldVnode) // 用 el 元素創建一個 oldVnode
}
const oldElm = oldVnode.elm // 就是傳入的 el元素
const parentElm = nodeOps.parentNode(oldElm) // el的父元素
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCB ? null : parentElm,
nodeOps.nextSibling(oldElm) // oldElm 的下一個兄弟節點
)
...
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
// 通過insert鈎子觸發組件的mounted鈎子
return vnode.elm // elm元素 返回給__patch__ ,賦值給vm.$el
}
重點分析 createElm 函數
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// vnode直接創建一個組件
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
...
// 利用 vnode 的 tag 屬性直接生成一個原生元素(div)
vnode.elm = nodeOps.createElement(tag, vnode)
}
createChildren(vnode, children, insertedVnodeQueue)
// 創建子節點,實際上也是調用 createElm
if (isDef(data)) {
// 插入vnode序列
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// 插入到父節點
insert(parentElm, vnode.elm, refElm)
} else if (isTrue(vnode.isComment)) {
// 注釋節點創建
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
// 文本節點
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
// createChildren
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
// 只接受數組類型
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(children)
}
for (let i = 0; i < children.length; ++i) {
// 實際上將父 vnode 的 elm 作為父元素傳入
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
// createComponent
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
// 調用hooks中的init鈎子,在 create-component.js中componentVNodeHooks
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm) // 插入到父元素 DOM
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
// src/core/vdom/create-component.js
// init鈎子函數
function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroy &&
vnode.data.keepAlive
) {
...
} else {
// createComponentInstanceForVnode 函數返回一個vm實例
// 實際上是調用了 vnode 的componentOptions.Ctor 來構造子組件
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// 上面生成的實例掛載(el是undefined)
child.$mount(hydrating ? vnode.elm : undefined, htdrating) // hydrating == false
}
}
// createComponentInstanceForVnode
// parent為當前的父vm,就是需要創建的vm的父vm == activeInstance
function createComponentInstanceForVnode (vnode, parent) {
const options = {
_isComponent: true,
_parentVnode: vnode,
parent
}
...
return new vnode.componentOptions.Ctor(options)
// Sub 構造函數的實例化 return vm
// 重新走一次 _init 流程
}
new Vue - vm.mount - mountComponent(vm) - vm._render(vmVnode) - vm.componentOptions - vm._update(vmVnode) - patch - createElm(vmVnode) - createComponent(vmVnode) - vm.hook.init - new child - vm.componentInstance - child.mount- createComponent(vmVnode) - insert
new child - child.mount - mountComponent(child) - child._update(childVnode) - patch - createElm(childVnode) - createElement(childVnode) - insert(child)