推荐答案
// www.javascriptcn.com code example
function patch(oldVNode, newVNode) {
if (!oldVNode) {
// 如果旧节点不存在,直接插入新节点
return document.body.appendChild(createElement(newVNode));
}
if (!newVNode) {
// 如果新节点不存在,移除旧节点
return oldVNode.remove();
}
if (isVNodeChanged(oldVNode, newVNode)) {
// 如果节点类型不同,直接替换
const newElement = createElement(newVNode);
oldVNode.parentNode.replaceChild(newElement, oldVNode);
return newElement;
}
if (newVNode.text) {
// 如果新节点是文本节点,直接更新文本内容
oldVNode.textContent = newVNode.text;
return oldVNode;
}
// 更新属性
updateAttributes(oldVNode, newVNode);
// 递归处理子节点
const oldChildren = oldVNode.childNodes;
const newChildren = newVNode.children || [];
const maxLen = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < maxLen; i++) {
patch(oldChildren[i], newChildren[i]);
}
return oldVNode;
}
function createElement(vnode) {
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
const element = document.createElement(vnode.tag);
updateAttributes(element, vnode);
if (vnode.children) {
vnode.children.forEach(child => {
element.appendChild(createElement(child));
});
}
return element;
}
function updateAttributes(element, vnode) {
const attrs = vnode.attrs || {};
for (let key in attrs) {
if (attrs.hasOwnProperty(key)) {
element.setAttribute(key, attrs[key]);
}
}
}
function isVNodeChanged(oldVNode, newVNode) {
return (
typeof oldVNode !== typeof newVNode ||
(typeof oldVNode === 'object' && oldVNode.tag !== newVNode.tag)
);
}本题详细解读
1. 函数功能
patch 函数用于将虚拟 DOM 的差异应用到真实 DOM 上。它通过递归比较新旧虚拟 DOM 节点,并根据差异进行相应的 DOM 操作,如插入、删除、替换、更新属性等。
2. 核心逻辑
节点不存在的情况:
- 如果旧节点不存在,直接插入新节点。
- 如果新节点不存在,移除旧节点。
节点类型不同:
- 如果新旧节点的类型不同(例如一个是
div,另一个是span),则直接替换整个节点。
- 如果新旧节点的类型不同(例如一个是
文本节点:
- 如果新节点是文本节点,直接更新旧节点的文本内容。
属性更新:
- 遍历新节点的属性,更新到旧节点上。
子节点递归处理:
- 递归处理新旧节点的子节点,确保所有子节点的差异都被正确应用。
3. 辅助函数
createElement:根据虚拟 DOM 节点创建真实 DOM 元素。updateAttributes:更新 DOM 元素的属性。isVNodeChanged:判断两个虚拟 DOM 节点是否不同。
4. 使用场景
该函数适用于需要手动操作 DOM 的场景,尤其是在实现虚拟 DOM 库或进行性能优化时。通过对比新旧虚拟 DOM 树,可以最小化 DOM 操作,提升页面渲染性能。
5. 注意事项
- 该实现是一个简化版本,实际应用中可能需要处理更多边界情况,如事件绑定、样式更新等。
- 递归处理子节点时,需要注意新旧子节点的长度可能不一致,需要确保所有子节点都被正确处理。