推荐答案
// www.javascriptcn.com code example
function diff(oldVNode, newVNode) {
// 如果两个节点类型不同,直接替换
if (oldVNode.type !== newVNode.type) {
return { type: 'REPLACE', node: newVNode };
}
// 如果两个节点都是文本节点,且内容不同,更新文本内容
if (typeof oldVNode === 'string' && typeof newVNode === 'string') {
if (oldVNode !== newVNode) {
return { type: 'TEXT', content: newVNode };
}
return null; // 没有变化
}
// 比较属性
const attrsDiff = {};
const oldAttrs = oldVNode.props || {};
const newAttrs = newVNode.props || {};
// 找出新增或修改的属性
for (let key in newAttrs) {
if (oldAttrs[key] !== newAttrs[key]) {
attrsDiff[key] = newAttrs[key];
}
}
// 找出删除的属性
for (let key in oldAttrs) {
if (!(key in newAttrs)) {
attrsDiff[key] = null;
}
}
// 如果有属性变化,返回属性差异
if (Object.keys(attrsDiff).length > 0) {
return { type: 'ATTR', attrs: attrsDiff };
}
// 比较子节点
const childrenDiff = [];
const oldChildren = oldVNode.children || [];
const newChildren = newVNode.children || [];
const maxLength = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < maxLength; i++) {
const childDiff = diff(oldChildren[i], newChildren[i]);
if (childDiff) {
childrenDiff.push({ index: i, ...childDiff });
}
}
// 如果有子节点变化,返回子节点差异
if (childrenDiff.length > 0) {
return { type: 'CHILDREN', children: childrenDiff };
}
// 没有变化
return null;
}本题详细解读
1. 节点类型不同
- 如果新旧虚拟 DOM 节点的类型不同(例如一个是
div,另一个是span),则直接返回一个REPLACE类型的差异对象,表示需要替换整个节点。
2. 文本节点
- 如果新旧节点都是文本节点,并且内容不同,则返回一个
TEXT类型的差异对象,表示需要更新文本内容。
3. 属性比较
- 遍历新旧节点的属性,找出新增、修改或删除的属性,并返回一个
ATTR类型的差异对象,表示需要更新属性。
4. 子节点比较
- 递归比较新旧节点的子节点,找出子节点的差异,并返回一个
CHILDREN类型的差异对象,表示需要更新子节点。
5. 没有变化
- 如果新旧节点完全相同,则返回
null,表示没有变化。
通过这种方式,diff 函数可以高效地比较两个虚拟 DOM 节点的差异,并返回一个描述差异的对象,供后续的 DOM 更新操作使用。