vue-next 對virtual dom的patch更新作了一系列的優化,從編譯時加入了 block 以減小 vdom 之間的對比次數,另外還有 hoisted 的操做減小了內存的開銷。本文寫給本身看,作個知識點記錄,若有錯誤,還請不吝賜教。html
VDOM的概念簡單來講就是用js對象來模擬真實DOM樹。因爲MV**的架構,真實DOM樹應該隨着數據(Vue2.x中的data)的改變而發生改變,這些改變多是如下幾個方面:vue
Vue框架要作的其實很單一:在用戶改變數據時,正確更新DOM樹,作法就是其核心的VDOM的patch和diff算法。node
在Vue2.x中,當數據改變後就要對全部的節點進行patch和diff操做。如如下DOM結構:算法
<div> <span class="header">I'm header</span> <ul> <li>第一個靜態li</li> <li v-for="item in mutableItems" :key="item.key"> {{ item.desc }}</li> </ul> </div>
在第一次mount節點的時候會去生成真實的DOM,此後若是數組
mutableItems.push({ key: 'asdf', desc: 'a new li item' })
預期的結果是頁面出現新的一個li元素,內容就是 a new li item,Vue2.x中是經過patch時對 ul
元素對應的 vnode
的 children
來進行 diff
操做,具體操做在此不深究,可是該操做是須要比較全部的 li
對應的 vnode
的。架構
正是因爲2.x版本中的diff操做須要遍歷全部元素,本例中包括了 span
和 第一個li
元素,可是這兩個元素是靜態的,不須要被比較的,不論數據怎麼變,靜態元素都不會再更改了。vue-next在編譯時對這種操做作了優化,即 Block
。框架
入上述模板,在vue-next中生成的渲染函數爲:dom
const _Vue = Vue const { createVNode: _createVNode } = _Vue const _hoisted_1 = _createVNode("span", { class: "header" }, "I'm header", -1 /* HOISTED */) const _hoisted_2 = _createVNode("li", null, "第一個靜態li", -1 /* HOISTED */) return function render(_ctx, _cache) { with (_ctx) { const { createVNode: _createVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString } = _Vue return (_openBlock(), _createBlock(_Fragment, null, [ _hoisted_1, _createVNode("ul", null, [ _hoisted_2, (_openBlock(true), _createBlock(_Fragment, null, _renderList(state.mutableItems, (item) => { return (_openBlock(), _createBlock("li", { key: item.key }, _toDisplayString(item.desc), 1 /* TEXT */)) }), 128 /* KEYED_FRAGMENT */)) ]) ], 64 /* STABLE_FRAGMENT */)) } }
咱們能夠看到調用了 openBlock
和 createBlock
方法,這兩個方法的代碼實現也很簡單:ide
const blockStack: (VNode[] | null)[] = [] let currentBlock: VNode[] | null = null let shouldTrack = 1 // openBlock export function openBlock(disableTracking = false) { blockStack.push((currentBlock = disableTracking ? null : [])) } export function createBlock( type: VNodeTypes | ClassComponent, props?: { [key: string]: any } | null, children?: any, patchFlag?: number, dynamicProps?: string[] ): VNode { // avoid a block with patchFlag tracking itself shouldTrack-- const vnode = createVNode(type, props, children, patchFlag, dynamicProps) shouldTrack++ // save current block children on the block vnode vnode.dynamicChildren = currentBlock || EMPTY_ARR // close block blockStack.pop() currentBlock = blockStack[blockStack.length - 1] || null // a block is always going to be patched, so track it as a child of its // parent block if (currentBlock) { currentBlock.push(vnode) } return vnode }
更加詳細的註釋還請看源代碼中的註釋,寫的十分詳盡,便於理解。這裏面 openBlock
就是初始化一個塊,createBlock
就是對當前編譯的內容生成一個塊,這裏面的這一行代碼:vnode.dynamicChildren = currentBlock || EMPTY_ARR
就是在收集動態的子節點,咱們能夠再看一下編譯時運行的函數:函數
// createVNode function _createVNode( type: VNodeTypes | ClassComponent, props: (Data & VNodeProps) | null = null, children: unknown = null, patchFlag: number = 0, dynamicProps: string[] | null = null ) { /** * 一系列代碼 **/ // presence of a patch flag indicates this node needs patching on updates. // component nodes also should always be patched, because even if the // component doesn't need to update, it needs to persist the instance on to // the next vnode so that it can be properly unmounted later. if ( shouldTrack > 0 && currentBlock && // the EVENTS flag is only for hydration and if it is the only flag, the // vnode should not be considered dynamic due to handler caching. patchFlag !== PatchFlags.HYDRATE_EVENTS && (patchFlag > 0 || shapeFlag & ShapeFlags.SUSPENSE || shapeFlag & ShapeFlags.STATEFUL_COMPONENT || shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) ) { currentBlock.push(vnode) } }
上述函數是在模板編譯成ast以後調用的生成VNode的函數,因此有patchFlag這個標誌,若是是動態的節點,而且此時是開啓了Block的話,就會將節點塞入Block中,這樣 createBlock
返回的 VNode
中就會有 dynamicChildren
了。
到此爲止,經過本文中案例通過模板編譯和render函數運行後並通過了優化之後生成了以下結構的vnode:
const result = { type: Symbol(Fragment), patchFlag: 64, children: [ { type: 'span', patchFlag: -1, ...}, { type: 'ul', patchFlag: 0, children: [ { type: 'li', patchFlag: -1, ...}, { type: Symbol(Fragment), children: [ { type: 'li', patchFlag: 1 ...}, { type: 'li', patchFlag: 1 ...} ] } ] } ], dynamicChildren: [ { type: Symbol(Fragment), patchFlag: 128, children: [ { type: 'li', patchFlag: 1 ...}, { type: 'li', patchFlag: 1 ...} ] } ] }
以上的 result
不完整,可是咱們暫時只關心這些屬性。能夠看見 result.children
的第一個元素是span,patchFlag=-1,且 result
有一個 dynamicChildren
數組,裏面只包含了兩個動態的 li
,後續若是變更了數據,那麼新的 vnode.dynamicChildren
會有第三個 li
元素。
patch部分其實也沒差多少,就是根據vnode的type執行不一樣的patch操做:
function patchElement(n1, n2) { let { dynamicChildren } = n2 // 一系列操做 if (dynamicChildren) { patchBlockChildren ( n1.dynamicChildren!, dynamicChildren, el, parentComponent, parentSuspense, areChildrenSVG ) } else if (!optimized) { // full diff patchChildren( n1, n2, el, null, parentComponent, parentSuspense, areChildrenSVG ) } }
能夠看見,若是有了 dynamicChildren
那麼vue2.x版本中的diff操做就被替換成了 patchBlockChildren()
且參數只有 dynamicChildren
,就是靜態的不作diff操做了,而若是vue-next的patch中沒有 dynamicChildren
,則進行完整的diff操做,入註釋寫的 full diff
的後續代碼。
本文沒有深刻講解代碼的實現層面,一是由於本身實力不濟還在閱讀源碼當中,二是我我的認爲閱讀源碼不可鑽牛角尖,從大局入眼,再徐徐圖之,先明白了各個部分的做用後帶着思考去閱讀源碼能收穫到的應該更多一些。
諸君共勉。