接着前文,咱們詳細研究了數據初始化的過程,也瞭解了數據更新的幾個步驟。如今進入到詳細的update過程,這個過程涉及到虛擬DOM與更新DOM操做的patch算法。node
在現代UI結構設計中(統稱MV*框架,V是現代的標記語言),數據驅動已經成爲一個核心。而引入虛擬DOM,則是數據驅動的一種實現方式。虛擬DOM(Virtual DOM)是對DOM的JS抽象表示,它們是JS對象,可以描述DOM結構和關係。應用 的各類狀態變化會做用於虛擬DOM,最終映射到DOM上。工做流程圖以下:算法
Vue 1.x中有細粒度的數據變化偵測,它是不須要虛擬DOM的,可是細粒度形成了大量開銷,這對於大 型項目來講是不可接受的。所以,Vue 2.x選擇了中等粒度的解決方案,每個組件一個Watcher實例, 這樣狀態變化時只能通知到組件,再經過引入虛擬DOM去進行比對和渲染。數組
能夠說,Vue2.x中引入虛擬DOM是必然的。設計結構發生了變化:組件與Watcher之間一一對應。這就要求必須引入虛擬DOM來應對該變化。bash
咱們先來看看Vue2.x中的虛擬DOM長啥樣。它的名字叫VNode:框架
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node // strictly internal raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function asyncMeta: Object | void; isAsyncPlaceholder: boolean; ssrContext: Object | void; fnContext: Component | void; // real context vm for functional nodes fnOptions: ?ComponentOptions; // for SSR caching devtoolsMeta: ?Object; // used to store functional render context for devtools fnScopeId: ?string; // functional scope id support 複製代碼
裏面的變量不少。可知這是一顆樹結構,children
裏面是Array<VNode>
。這是怎麼生成的呢?咱們回憶以前解讀源碼中的$mount
過程,找一個切入點。dom
從core/instance/lifecycle.js
中的mountComponent()
開始:async
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
...//省略
let updateComponent = () => {
//更新 Component的定義,主要作了兩個事情:render(生成vdom)、update(轉換vdom爲dom)
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
...//省略
}
複製代碼
看到在new Watcher實例時,與updateComponent建立了關聯。重點關注vm._render()
。函數
在core/instance/render.js
內:oop
import { createElement } from '../vdom/create-element'
export function initRender (vm: Component) {
...//省略
//編譯器使用的render
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
//用戶編寫的render,典型的柯里化
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
...//省略
}
Vue.prototype._render = function (): VNode {
const vm: Component = this
...//省略
//從選項中獲取render函數
const { render, _parentVnode } = vm.$options
// 最終計算出的虛擬DOM
let vnode
// 執行render函數,傳入參數是$createElement (經常使用的render()方法中的h參數)
let vnode = render.call(vm._renderProxy, vm.$createElement)
...//省略
return vnode
}
複製代碼
看到了VNode。這個過程執行了render函數,用到了createElement()
方法。看來建立VNode的核心在這個方法裏面。關聯到core/vdom/create-element.js
:性能
//返回VNode或者由VNode構成的數組
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
...//省略
return _createElement(context, tag, data, children, normalizationType)
}
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
...//省略
//核心: vnode的生成過程
//傳入tag多是原生的HTML標籤,也多是用戶自定義標籤
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
//是原生保留標籤,直接建立VNode
if (config.isReservedTag(tag)) {
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
}else if((!data||!data.pre)&&isDef(Ctor=resolveAsset(context.$options, 'components',tag))) {
// 自定義組件,區別對待,須要先建立組件,再建立VNode
vnode = createComponent(Ctor, data, context, children, tag)
} else {
//
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
...//省略
}
複製代碼
整個流程串起來,咱們看到:render函數經過調用createElement()
方法,對不一樣傳入的參數類型進行加工,最終獲得了VNode樹。流程圖以下:
那麼新舊VNode之間如何比較變化,進而以最小代價執行真實DOM的更新呢?咱們下篇文章的patch算法將會講到。