element.js 生成虚拟dom
import utils from './utils.js'
class Element {
constructor(tagName,attrs,children) {
this.tagName = tagName;
this.attrs = attrs;
this.children = children || [];
}
render(){
let element = document.createElement(this.tagName);
for(let attr in this.attrs){
utils.setAttr(element, attr,this.attrs[attr]);
}
this.children.forEach(child => {
let childElement = (child instanceof Element) ? child.render() : document.createTextNode(child);
element.appendChild(childElement)
})
return element;
}
}
function createElement(tagName,attrs,children) {
return new Element(tagName,attrs,children);
}
export {
createElement
}
diff.js 进行dom diff比较生成补丁包
import utils from './utils.js';
let keyIndex = 0;
function diff(oldTree, newTree) {
let patches = {};
keyIndex = 0;
let index = 0;
walk(oldTree, newTree, index, patches);
return patches;
}
function walk(oldNode, newNode, index, patches) {
let currentPatches = [];
if (!newNode) {
currentPatches.push({ type: utils.REMOVE, index });
} else if (utils.isString(oldNode) && utils.isString(newNode)) {
if (oldNode != newNode) {
currentPatches.push({ type: utils.TEXT, content: newNode });
}
} else if (oldNode.tagName == newNode.tagName) {
let attrsPatch = diffAttr(oldNode.attrs, newNode.attrs);
if (Object.keys(attrsPatch).length > 0) {
currentPatches.push({ type: utils.ATTRS, attrs: attrsPatch });
}
diffChildren(oldNode.children, newNode.children, index, patches, currentPatches);
} else {
currentPatches.push({ type: utils.REPLACE, node: newNode });
}
if (currentPatches.length > 0) {
patches[index] = currentPatches;
}
}
function diffChildren(oldChildren, newChildren, index, patches, currentPatches) {
oldChildren.forEach((child, idx) => {
walk(child, newChildren[idx], ++keyIndex, patches);
});
}
function diffAttr(oldAttrs, newAttrs) {
let attrsPatch = {};
for (let attr in oldAttrs) {
if (oldAttrs[attr] != newAttrs[attr]) {
console.log(1)
attrsPatch[attr] = newAttrs[attr];
}
}
for (let attr in newAttrs) {
if (!oldAttrs.hasOwnProperty(attr)) {
attrsPatch[attr] = newAttrs[attr];
}
}
return attrsPatch;
}
export{diff} ;
patch.js(补丁包,将虚拟dom转成真实dom)
import utils from "./utils.js";
let keyIndex = 0;
let allPatches;
function patch(root, patches) {
allPatches = patches;
walk(root);
}
function walk(node) {
let currentPatches = allPatches[keyIndex++];
(node.childNodes || []).forEach((child) => walk(child));
if (currentPatches) {
doPatch(node, currentPatches);
}
}
function doPatch(node, currentPatches) {
currentPatches.forEach((patch) => {
switch (patch.type) {
case utils.ATTRS:
for (let attr in patch.attrs) {
let value = patch.attrs[attr];
if (value) {
utils.setAttr(node, attr, value);
} else {
node.removeAttribute(attr);
}
}
break;
case utils.TEXT:
node.textContent = patch.content;
break;
case utils.REPLACE:
let newNode =
patch.node instanceof Element
? path.node.render()
: document.createTextNode(path.node);
node.parentNode.replaceChild(newNode, node);
break;
case utils.REMOVE:
node.parentNode.removeChild(node);
break;
}
});
}
export { patch };
utils.js工具函数
let utils = {
REMOVE: 'REMOVE',
ATTRS: "ATTRS",
TEXT: "TEXT",
REPLACE: "REPLACE",
setAttr(element, attr, value) {
switch (attr) {
case 'style':
element.style.cssText = value;
break;
case 'value':
let tagName = element.tagName.toLowerCase();
if (tagName == 'input' || tagName == 'textarea') {
element.value = value;
} else {
element.setAttribute(attr, value);
}
break;
default:
element.setAttribute(attr, value);
break;
}
},
type(obj) {
return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g, '');
},
isString(str) {
return utils.type(str) == 'String';
}
}
export default utils;
index.js 入口文件
import { createElement } from "./element.js";
import { diff } from "./diff.js";
import { patch} from './patch.js';
let dom1 = createElement("ul", { class: "list" }, [
createElement("li", { class: "item" }, ["1"]),
createElement("li", { class: "item" }, ["2"]),
createElement("li", { class: "item" }, ["3"]),
createElement("li", { class: "item" }, ["4"]),
]);
let root = dom1.render();
document.body.appendChild(root)
let dom2 = createElement("ul", { class: "list-new" }, [
createElement("li", { class: "item" }, ["1"]),
createElement("li", { class: "item" }, ["9"]),
createElement("li", { class: "item" }, ["3"]),
]);
let patches = diff(dom1, dom2);
console.log('补丁包',patches)
patch(root,patches)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dom diff</title>
</head>
<body>
<script type="module" src="./index.js"></script>
</body>
</html>