嘗試梳理分析,若有錯誤,請多多指正,謝謝~node
來到最後一章的梳理了,接下來就是分析patch
的操做。patch
操做就是根據patchObj
將當前掛載的DOM
真實節點進行操做(增刪改),也就是常常說的,將不少次操做合併爲一次對DOM
的操做。上一節已經經過diff
拿到了patchObj
,來看看patch
是怎麼使用這個東西的吧。git
先從入口看,經過patch.js
看到,調用的是/vdom/patch.js
,patch
函數實現以下: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
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
}
複製代碼
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
}
複製代碼
VD Tree
和DOM
獲取到對於的DOM
對象的index
數組。domIndex
函數大體的操做就是將DOM
的順序根據下標升序排序,而後經過DOM
和VD 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)
複製代碼
options
和默認的判斷,拿到頂級元素實例,這裏通常是document
對象。var ownerDocument = rootNode.ownerDocument
if (!renderOptions.document && ownerDocument !== document) {
renderOptions.document = ownerDocument
}
複製代碼
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)
}
複製代碼
根據以上四個步驟,能夠看到當前這個函數是將子元素的相關信息拿出來進行了規整,而後在繼續往下走。瀏覽器
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
}
複製代碼
DOM
實例爲空(也就是一個一個子元素)則直接返回。if (!domNode) {
return rootNode
}
複製代碼
patchObj
是一個數組,則循環調用patchOp
函數,若是不是就直接調用,這裏也就是對應的是子元素是多個和單個的時候。newNode = patchOp(patchList[i], domNode, renderOptions)
複製代碼
最終調用的是patchOp
函數,這個函數就是去真是的操做DOM
了。因此最簡單也是最核心的東西就裏面。bash
這裏的applyPatch
的函數經過patchOp
操做完DOM
後,就能夠直接返回獲得的rootNode
了。app
/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
值,來完成相應的變動。函數
這裏看幾個經常使用的操做remove
、insert
和replace
操做。
function removeNode(domNode, vNode) {
var parentNode = domNode.parentNode
if (parentNode) {
parentNode.removeChild(domNode)
}
destroyWidget(domNode, vNode);
return null
}
複製代碼
首先拿到該元素的parentNode
,而後調用DOM
的removeChild
方法,將元素刪除,很直接。
function insertNode(parentNode, vNode, renderOptions) {
var newNode = renderOptions.render(vNode, renderOptions)
if (parentNode) {
parentNode.appendChild(newNode)
}
return parentNode
}
複製代碼
首先經過renderOptions.render
函數建立DOM
,render
以前賦值過,默認是調用createElement
方法,具體是怎麼建立的,請看第一節。建立了newNode
後,將元素插入到當前等元素位置是上。
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
,再判斷是否與以前的 相同,若是不一樣的話,就直接調用DOM
的replaceChild
函數替換子元素。
經過例舉了三個經常使用的操做,瞭解到其實最終都是直接調用DOM
提供的一些元素操做方法來完成,也是理所固然的,必定會調用這些,如今能夠明白了是怎麼調用。到這裏 virtual-dom
的流程算是梳理完了,知道了從建立 VD
到最後渲染和更新的具體操做,想本身動手實現一個 virtual-dom
推薦看看下面這文章:segmentfault.com/a/119000001…