virtual-dom 梳理分析【patch函數】

嘗試梳理分析,若有錯誤,請多多指正,謝謝~node

來到最後一章的梳理了,接下來就是分析patch的操做。patch操做就是根據patchObj將當前掛載的DOM真實節點進行操做(增刪改),也就是常常說的,將不少次操做合併爲一次對DOM的操做。上一節已經經過diff拿到了patchObj,來看看patch是怎麼使用這個東西的吧。git

patch 函數

先從入口看,經過patch.js看到,調用的是/vdom/patch.jspatch函數實現以下:github

function patch(rootNode, patches, renderOptions) {
    renderOptions = renderOptions || {}
    renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch
        ? renderOptions.patch
        : patchRecursive
    renderOptions.render = renderOptions.render || render

    return renderOptions.patch(rootNode, patches, renderOptions)
}
複製代碼

能夠看到接收三個參數,第一個是真實的DOM,第二個是diff獲得的patchObj對象,第三個就是附加的參數。代碼裏面對參數進行了判斷,也就是說patch函數能夠是本身傳入的patch,可是沒傳默認使用patchRecursive函數,因此緊跟着咱們來看一下這個函數。segmentfault

patchRecursive 函數

function patchRecursive(rootNode, patches, renderOptions) {
    var indices = patchIndices(patches)

    if (indices.length === 0) {
        return rootNode
    }

    var index = domIndex(rootNode, patches.a, indices)
    var ownerDocument = rootNode.ownerDocument

    if (!renderOptions.document && ownerDocument !== document) {
        renderOptions.document = ownerDocument
    }

    for (var i = 0; i < indices.length; i++) {
        var nodeIndex = indices[i]
        rootNode = applyPatch(rootNode,
            index[nodeIndex],
            patches[nodeIndex],
            renderOptions)
    }

    return rootNode
}
複製代碼
  1. 拿出patchObj裏面的下標。上一節說到patchObj的造成過程,最終張這個樣子{ a: {...}, 0: {...}, 1: {...}}a裏面對應的是上一個VD Tree,其餘數值下標就是當前root的每個子元素的patchObj。如圖。因此indices的值應該相似於這樣[0, 1...]。若是這個數組爲空,則說明root`下面沒有子元素就直接返回了,不須要再作掛載。
function patchIndices(patches) {
    var indices = []

    for (var key in patches) {
        if (key !== "a") {
            indices.push(Number(key))
        }
    }

    return indices
}
複製代碼

patchObj

  1. 根據以前的VD TreeDOM獲取到對於的DOM對象的index數組。domIndex函數大體的操做就是將DOM的順序根據下標升序排序,而後經過DOMVD Tree進行對比,遞歸去整理每一個元素的子元素等操做。官方的註釋以下:

Maps a virtual DOM tree onto a real DOM tree in an efficient manner. We don't want to read all of the DOM nodes in the tree so we use the in-order tree indexing to eliminate recursion down certain branches. We only recurse into a DOM node if we know that it contains a child of interest. 大體意思以下:eyes:: 以一種有效的方式將虛擬DOM樹映射到真實的DOM樹。 咱們不想讀取樹中的全部DOM節點,因此使用 按順序樹索引,以消除某些分支上的遞歸。 咱們只有在知道一個DOM節點包含一個子節點時才遞歸到它數組

var index = domIndex(rootNode, patches.a, indices)
複製代碼
  1. 根據傳入如的options和默認的判斷,拿到頂級元素實例,這裏通常是document對象。
var ownerDocument = rootNode.ownerDocument

if (!renderOptions.document && ownerDocument !== document) {
    renderOptions.document = ownerDocument
}
複製代碼
  1. 最後循環indices數組來根據patchObj來掛載,目光再次轉移到applyPatch函數,每次循環都是去執行的這個函數。取出每一個子節點的DOM實例,patchObj實例和整個DOM樹傳入到applyPatch去執行。
for (var i = 0; i < indices.length; i++) {
   var nodeIndex = indices[i]
   rootNode = applyPatch(rootNode,
            index[nodeIndex],
            patches[nodeIndex],
            renderOptions)
}
複製代碼

根據以上四個步驟,能夠看到當前這個函數是將子元素的相關信息拿出來進行了規整,而後在繼續往下走。瀏覽器

applyPatch 函數

function applyPatch(rootNode, domNode, patchList, renderOptions) {
    if (!domNode) {
        return rootNode
    }

    var newNode

    if (isArray(patchList)) {
        for (var i = 0; i < patchList.length; i++) {
            newNode = patchOp(patchList[i], domNode, renderOptions)

            if (domNode === rootNode) {
                rootNode = newNode
            }
        }
    } else {
        newNode = patchOp(patchList, domNode, renderOptions)

        if (domNode === rootNode) {
            rootNode = newNode
        }
    }

    return rootNode
}
複製代碼
  1. 若是傳入的DOM實例爲空(也就是一個一個子元素)則直接返回。
if (!domNode) {
    return rootNode
}
複製代碼
  1. 若是 patchObj 是一個數組,則循環調用patchOp函數,若是不是就直接調用,這裏也就是對應的是子元素是多個和單個的時候。
newNode = patchOp(patchList[i], domNode, renderOptions)
複製代碼

最終調用的是patchOp函數,這個函數就是去真是的操做DOM了。因此最簡單也是最核心的東西就裏面。bash

這裏的applyPatch的函數經過patchOp操做完DOM後,就能夠直接返回獲得的rootNode了。app

patchOp 函數

/vdom/patch-op.js 文件有 152 行,之後可能會改動吧,只是想表達,這實際操做DOM的函數其實並不複雜,也不能複雜,由於就是去修改和刪除DOM等,也就是瀏覽器提供的操做函數而已。dom

function applyPatch(vpatch, domNode, renderOptions) {
    var type = vpatch.type
    var vNode = vpatch.vNode
    var patch = vpatch.patch

    switch (type) {
        case VPatch.REMOVE:
            return removeNode(domNode, vNode)
        case VPatch.INSERT:
            return insertNode(domNode, patch, renderOptions)
        case VPatch.VTEXT:
            return stringPatch(domNode, vNode, patch, renderOptions)
        case VPatch.WIDGET:
            return widgetPatch(domNode, vNode, patch, renderOptions)
        case VPatch.VNODE:
            return vNodePatch(domNode, vNode, patch, renderOptions)
        case VPatch.ORDER:
            reorderChildren(domNode, patch)
            return domNode
        case VPatch.PROPS:
            applyProperties(domNode, patch, vNode.properties)
            return domNode
        case VPatch.THUNK:
            return replaceRoot(domNode,
                renderOptions.patch(domNode, patch, renderOptions))
        default:
            return domNode
    }
}
複製代碼

能夠很明確的看到,這裏經過switch判斷patchObj裏面的type值,來完成相應的變動。函數

這裏看幾個經常使用的操做removeinsertreplace操做。

remove

function removeNode(domNode, vNode) {
    var parentNode = domNode.parentNode

    if (parentNode) {
        parentNode.removeChild(domNode)
    }

    destroyWidget(domNode, vNode);

    return null
}
複製代碼

首先拿到該元素的parentNode,而後調用DOMremoveChild方法,將元素刪除,很直接。

insert

function insertNode(parentNode, vNode, renderOptions) {
    var newNode = renderOptions.render(vNode, renderOptions)

    if (parentNode) {
        parentNode.appendChild(newNode)
    }

    return parentNode
}
複製代碼

首先經過renderOptions.render函數建立DOMrender以前賦值過,默認是調用createElement方法,具體是怎麼建立的,請看第一節。建立了newNode後,將元素插入到當前等元素位置是上。

replace

function vNodePatch(domNode, leftVNode, vNode, renderOptions) {
    var parentNode = domNode.parentNode
    var newNode = renderOptions.render(vNode, renderOptions)

    if (parentNode && newNode !== domNode) {
        parentNode.replaceChild(newNode, domNode)
    }

    return newNode
}
複製代碼

一樣的,先拿到parentNode元素,而後經過creatElement生成先的DOM,再判斷是否與以前的 相同,若是不一樣的話,就直接調用DOMreplaceChild函數替換子元素。

小結

經過例舉了三個經常使用的操做,瞭解到其實最終都是直接調用DOM提供的一些元素操做方法來完成,也是理所固然的,必定會調用這些,如今能夠明白了是怎麼調用。到這裏 virtual-dom 的流程算是梳理完了,知道了從建立 VD 到最後渲染和更新的具體操做,想本身動手實現一個 virtual-dom 推薦看看下面這文章:segmentfault.com/a/119000001…

原文地址

相關文章
相關標籤/搜索