1.概念:
-
Virtual DOM 是真实DOM的映射。
-
当虚拟 DOM 树中的某些节点改变时,会得到一个新的虚拟树。算法对这两棵树(新树和旧树)进行比较,找出差异,然后只需要在真实的 DOM 上做出相应的改变。
2.设计思路:
- 虚拟Dom映射到真实Dom的工具函数 createElement(…) ,将js对象映射为Dom对象
- 比较两棵虚拟DOM树的差异并更新的工具函数updateElement(…) ,包括新节点的添加,老节点的移除以及节点的替换;
3.实现:
-
createElement(…),利用document.createTextNode 、document.createElement实现文本节点和dom节点的基本创建
/**
* @description 传入node类型的js对象数据,生成Dom
* @param node node类型js对象数据 节点数据
*/
function createElement(node) {
if (typeof node === 'string') {
return document.createTextNode(node);
} else {
const $el = document.createElement(node.type);
node.children.map(createElement).forEach($el.appendChild.bind($el));
return $el;
}
node的类型为:
const node = {
type: "div", // 标签名或者是纯文本
props: {}, // 扩展的功能,如class、id等属性的添加
children: [],// node 类型的js对象数组
};
-
updateElement(…),利用createElement,添加一些条件判断 ,实现dom树的渲染、更新,应当考虑3个参数,$parent, newNode, oldNode ,稍后介绍,$parent是父节点
首先是新节点的添加:
// 不存在老节点,则直接创建新节点
function updateElement($parent, newNode, oldNode,index) {
if (!oldNode) {
$parent.appendChild(createElement(newNode));
}
}
然后是存在oldNode存在而newNode为空的情况:
// 实现移除老节点的操作,index为该节点在父节点的子节点数组中的位序
function updateElement($parent, newNode, oldNode, index) {
if (!oldNode) {
$parent.appendChild(createElement(newNode));
} else if (!newNode) {
$parent.removeChild($parent.childNodes[index]);
}
}
当oldNode,newNode都存在时:
// 节点的替换操作,
function updateElement($parent, newNode, oldNode, index = 0) {
if (!oldNode) {
$parent.appendChild(createElement(newNode));
} else if (!newNode) {
$parent.removeChild($parent.childNodes[index]);
} else if (changed(newNode, oldNode)) {
$parent.replaceChild(createElement(newNode), $parent.childNodes[index]);
}
}
// 需要定义一个工具函数changed(newNode, oldNode),用于比较新老节点之间的差异
function changed(node1, node2) {
return typeof node1 !== typeof node2 ||
typeof node1 === ‘string’ && node1 !== node2 ||
node1.type !== node2.type
}
当新旧节点没有差异时,需要调用updateElement实现递归操作其子节点:
// 完整的节点创建、更新函数
function updateElement($parent, newNode, oldNode, index) {
if (!oldNode) {
$parent.appendChild(createElement(newNode));
} else if (!newNode) {
$parent.removeChild($parent.childNodes[index]);
} else if (changed(newNode, oldNode)) {
$parent.replaceChild(createElement(newNode), $parent.childNodes[index]);
} else if (newNode.type) {
const newLength = newNode.children.length;
const oldLength = oldNode.children.length;
for (let i = 0; i < newLength || i < oldLength; i++) {
updateElement(
$parent.childNodes[index],
newNode.children[i],
oldNode.children[i],
i
);
}
}
}