var _ = require('./util')
var patch = require('./patch')
var listDiff = require('list-diff2')
function diff (oldTree, newTree) {
var index = 0
var patches = {}
dfsWalk(oldTree, newTree, index, patches)
return patches
}
function dfsWalk (oldNode, newNode, index, patches) {
var currentPatch = []
// Node is removed.
if (newNode === null) {
// Real DOM node will be removed when perform reordering, so has no needs to do anthings in here
// TextNode content replacing
} else if (_.isString(oldNode) && _.isString(newNode)) {
if (newNode !== oldNode) {
currentPatch.push({ type: patch.TEXT, content: newNode })
}
// Nodes are the same, diff old node's props and children
} else if (
oldNode.tagName === newNode.tagName &&
oldNode.key === newNode.key
) {
// Diff props
var propsPatches = diffProps(oldNode, newNode)
if (propsPatches) {
currentPatch.push({ type: patch.PROPS, props: propsPatches })
}
// Diff children. If the node has a `ignore` property, do not diff children
if (!isIgnoreChildren(newNode)) {
diffChildren(
oldNode.children,
newNode.children,
index,
patches,
currentPatch
)
}
// Nodes are not the same, replace the old node with new node
} else {
currentPatch.push({ type: patch.REPLACE, node: newNode })
}
if (currentPatch.length) {
patches[index] = currentPatch
}
}
function diffChildren (oldChildren, newChildren, index, patches, currentPatch) {
var diffs = listDiff(oldChildren, newChildren, 'key')
newChildren = diffs.children
if (diffs.moves.length) {
var reorderPatch = { type: patch.REORDER, moves: diffs.moves }
currentPatch.push(reorderPatch)
}
var leftNode = null
var currentNodeIndex = index
_.each(oldChildren, function (child, i) {
var newChild = newChildren[i]
currentNodeIndex = (leftNode && leftNode.count)
? currentNodeIndex + leftNode.count + 1
: currentNodeIndex + 1
dfsWalk(child, newChild, currentNodeIndex, patches)
leftNode = child
})
}
function diffProps (oldNode, newNode) {
var count = 0
var oldProps = oldNode.props
var newProps = newNode.props
var key, value
var propsPatches = {}
// Find out different properties
for (key in oldProps) {
value = oldProps[key]
if (newProps[key] !== value) {
count++
propsPatches[key] = newProps[key]
}
}
// Find out new property
for (key in newProps) {
value = newProps[key]
if (!oldProps.hasOwnProperty(key)) {
count++
propsPatches[key] = newProps[key]
}
}
// If properties all are identical
if (count === 0) {
return null
}
return propsPatches
}
function isIgnoreChildren (node) {
return (node.props && node.props.hasOwnProperty('ignore'))
}
module.exports = diff
3.差异应用到真正的DOM树
patch.js
var _ = require('./util')
var REPLACE = 0
var REORDER = 1
var PROPS = 2
var TEXT = 3
function patch (node, patches) {
var walker = {index: 0}
dfsWalk(node, walker, patches)
}
function dfsWalk (node, walker, patches) {
var currentPatches = patches[walker.index]
var len = node.childNodes
? node.childNodes.length
: 0
for (var i = 0; i < len; i++) {
var child = node.childNodes[i]
walker.index++
dfsWalk(child, walker, patches)
}
if (currentPatches) {
applyPatches(node, currentPatches)
}
}
function applyPatches (node, currentPatches) {
_.each(currentPatches, function (currentPatch) {
switch (currentPatch.type) {
case REPLACE:
var newNode = (typeof currentPatch.node === 'string')
? document.createTextNode(currentPatch.node)
: currentPatch.node.render()
node.parentNode.replaceChild(newNode, node)
break
case REORDER:
reorderChildren(node, currentPatch.moves)
break
case PROPS:
setProps(node, currentPatch.props)
break
case TEXT:
if (node.textContent) {
node.textContent = currentPatch.content
} else {
// fuck ie
node.nodeValue = currentPatch.content
}
break
default:
throw new Error('Unknown patch type ' + currentPatch.type)
}
})
}
function setProps (node, props) {
for (var key in props) {
if (props[key] === void 666) {
node.removeAttribute(key)
} else {
var value = props[key]
_.setAttr(node, key, value)
}
}
}
function reorderChildren (node, moves) {
var staticNodeList = _.toArray(node.childNodes)
var maps = {}
_.each(staticNodeList, function (node) {
if (node.nodeType === 1) {
var key = node.getAttribute('key')
if (key) {
maps[key] = node
}
}
})
_.each(moves, function (move) {
var index = move.index
if (move.type === 0) { // remove item
if (staticNodeList[index] === node.childNodes[index]) { // maybe have been removed for inserting
node.removeChild(node.childNodes[index])
}
staticNodeList.splice(index, 1)
} else if (move.type === 1) { // insert item
var insertNode = maps[move.item.key]
? maps[move.item.key].cloneNode(true) // reuse old item
: (typeof move.item === 'object')
? move.item.render()
: document.createTextNode(move.item)
staticNodeList.splice(index, 0, insertNode)
node.insertBefore(insertNode, node.childNodes[index] || null)
}
})
}
patch.REPLACE = REPLACE
patch.REORDER = REORDER
patch.PROPS = PROPS
patch.TEXT = TEXT
module.exports = patch
对一个virtual dom实现的代码做的整理,原文见:https://github.com/livoras/blog/issues/13 1.模拟dom树 element.js
使用: var ul = el('ul', {id: 'list'}, [ el('li', {class: 'item'}, ['Item 1']), el('li', {class: 'item'}, ['Item 2']), el('li', {class: 'item'}, ['Item 3']) ]) var ulRoot = ul.render() document.body.appendChild(ulRoot) 生成的dom结果如下: `
`
虚拟dom树差异比对 每一层进行对比:复杂度O(n) diff.js
3.差异应用到真正的DOM树 patch.js