<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>简单 Diff算法</title>
<style>
.bgColor {
background-color: deeppink;
}
.fontColor {
color: #fff;
font-size: 24px;
}
</style>
</head>
<body>
<div id="app"></div>
<script>
/**
* 定义文本节点和注释节点
*/
const Text = Symbol();
const Comment = Symbol();
const Fragment = Symbol();
const newVnode1 = {
type: Text,
children: "我是文本内容 ",
};
const newVnode2 = {
type: Comment,
children: "我是注释内容",
};
const fragmentNode = {
type: "ul",
children: [
{
type: Fragment,
children: [
{ type: "li", children: "item 1" },
{ type: "li", children: "item 2" },
{ type: "li", children: "item 3" },
],
},
],
};
/**
* 自定义渲染器
*/
function createRenderer(options) {
const {
createElement,
insert,
setElementText,
patchProps,
createText,
setText,
} = options;
/**
* 卸载
*/
function unmount(vnode) {
if (vnode.type === Fragment) {
vnode.children.forEach((c) => unmount(c));
return;
}
const parent = vnode.el.parentNode;
if (parent) {
parent.removeChild(vnode.el);
}
}
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container);
} else {
// 删除旧节点
if (container._vnode) {
unmount(container._vnode);
}
}
container._vnode = vnode;
}
/**
* 打补丁
*/
function patch(n1, n2, container, anchor) {
if (n1 && n1.type !== n2.type) {
unmount(n1);
n1 = null;
}
const { type } = n2;
// console.log(type);
if (typeof type === "string") {
if (!n1) {
mountElement(n2, container, anchor);
} else {
patchElement(n1, n2);
}
} else if (type === Text) {
// console.log("文本节点");
// 文本节点
if (!n1) {
const el = (n2.el = createText(n2.children));
insert(el, container);
} else {
const el = (n2.el = n1.el);
if (n2.children !== n1.children) {
setText(el, n2.children);
}
}
} else if (type === Fragment) {
// 片断
if (!n1) {
// 将Fragment的 children逐个挂载
n2.children.forEach((c) => patch(null, c, container));
} else {
patchChildren(n1, n2, container);
}
}
}
/**
* 更新子节点
*/
function patchElement(n1, n2) {
const el = (n2.el = n1.el);
const oldProps = n1.props;
const newProps = n2.props;
for (const key in newProps) {
if (newProps[key] !== oldProps[key]) {
patchProps(el, key, oldProps[key], newProps[key]);
}
}
for (const key in oldProps) {
if (!(key in newProps)) {
patchProps(el, key, oldProps[key], null);
}
}
patchChildren(n1, n2, el);
}
/**
* 抽离封装核心 Diff算法
*/
function diff(n1, n2, container) {
console.log("此处是核心的 Diff算法");
console.log(n1);
console.log(n2);
const oldChildren = n1.children;
const newChildren = n2.children;
let lastIndex = 0;
for (let i = 0; i < newChildren.length; i++) {
const newVNode = newChildren[i];
let find = false;
for (let j = 0; j < oldChildren.length; j++) {
const oldVNode = oldChildren[j];
if (newVNode.key === oldVNode.key) {
// 找到了可复用的节点
find = true;
patch(oldVNode, newVNode, container);
if (j < lastIndex) {
// 移动真实DOM节点
const prevVNode = newChildren[i - 1];
if (prevVNode) {
const anchor = prevVNode.el.nextSibling;
insert(newVNode.el, container, anchor);
}
} else {
lastIndex = j;
}
break;
}
}
// 添加新元素
if (!find) {
const prevVNode = newChildren[i - 1];
let anchor = null;
if (prevVNode) {
anchor = prevVNode.el.nextSibling;
} else {
prevVNode = container.firstChild;
}
// 挂载 newVNode
patch(null, newVNode, container, anchor);
}
// 移除不存在的元素
for (let i = 0; i < oldChildren.length; i++) {
const oldVNode = oldChildren[i];
const has = newChildren.find(
(vnode) => vnode.key === oldVNode.key
);
if (!has) {
unmount(oldVNode);
}
}
}
}
/**
* 更新children
*/
function patchChildren(n1, n2, container) {
if (typeof n2.children === "string") {
if (Array.isArray(n1.children)) {
n1.children.forEach((c) => unmount(c));
}
setElementText(container, n2.children);
} else if (Array.isArray(n2.children)) {
if (Array.isArray(n1.children)) {
// 此处是核心的 Diff算法
diff(n1, n2, container);
} else {
setElementText(container, "");
// 挂载新节点
n2.children.forEach((c) => patch(null, c, container));
}
} else {
// 新子节点不存在,则卸载旧节点
if (Array.isArray(n1.children)) {
n1.children.forEach((c) => unmount(c));
} else if (typeof n1.children === "string") {
setElementText(container, "");
}
}
}
// 主要改动这里
function mountElement(vnode, container, anchor) {
const el = (vnode.el = createElement(vnode.type));
if (typeof vnode.children === "string") {
setElementText(el, vnode.children);
} else if (Array.isArray(vnode.children)) {
vnode.children.forEach((child) => {
patch(null, child, el);
});
}
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
patchProps(el, key, null, value);
}
}
insert(el, container, anchor);
}
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "INPUT") {
return false;
}
return key in el;
}
return { render };
}
/**
* 公共函数
*/
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "INPUT") {
console.log("el", el);
return false;
}
return key in el;
}
// 测试代码
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
parent.insertBefore(el, anchor);
},
createText(text) {
return document.createTextNode(text);
},
setText(el, text) {
el.nodeValue = text;
},
patchProps(el, key, prevValue, nextValue) {
if (/^on/.test(key)) {
// const name = key.slice(2).toLowerCase();
// prevValue && el.removeEventListener(name, prevValue)
// el.addEventListener(name, nextValue);
const invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
const name = key.slice(2).toLowerCase();
if (nextValue) {
if (!invoker) {
// 绑定一个伪造的事件处理函数invoker
invoker = el._vei[key] = (e) => {
// e.timeStamp 触发事件的时间
if (e.timeStamp < invoker.attached) {
return;
}
if (Array.isArray(invoker.value)) {
invoker.value.forEach((fn) => fn(e));
} else {
invoker.value(e);
}
};
invoker.value = nextValue;
// 绑定事件的时间
invoker.attached = performance.now();
el.addEventListener(name, invoker);
} else {
invoker.value = nextValue;
}
} else {
el.removeEventListener(name, invoker);
}
} else if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, nextValue);
}
},
});
// 虚拟节点对象
const oldVnode = {
type: "div",
children: [
{ type: "p", children: "1", key: 1 },
{ type: "p", children: "2", key: 2 },
{ type: "p", children: "hello", key: 3 },
],
};
const newVnode = {
type: "div",
children: [
{ type: "p", children: "world", key: 3 },
{ type: "p", children: "1", key: 1 },
{ type: "p", children: "4", key: 4 },
// { type: "p", children: "2", key: 2 },
],
};
renderer.render(oldVnode, document.querySelector("#app"));
setTimeout(() => {
renderer.render(newVnode, document.querySelector("#app"));
}, 2000);
</script>
</body>
</html>