因为虚拟dom是用babel进行转义的,所以这里我简单直接写入虚拟dom的格式
// 创建虚拟dom
class Element {
constructor(type, props, children) {
this.type = type;
this.props = props;
this.children = children;
}
}
function createElement(type, props, children) {
return new Element(type, props, children);
}
// 将虚拟dom渲染成真实dom
function render(dom) {
const tag = document.createElement(dom.type);
setAttr(tag, dom.props);
// 遍历children
dom.children &&
dom.children.forEach(item => {
if (item instanceof Element) {
tag.appendChild(render(item));
} else {
tag.textContent = item;
}
});
return tag;
}
// 设置属性
function setAttr(tag, attr) {
for (let i in attr) {
// if else的判断是为了增添删除属性
if (i === "className") {
if (attr[i]) {
tag.setAttribute("class", attr[i]);
} else {
tag.removeAttribute("class");
}
} else if (i === "style") {
if (attr[i]) {
Object.assign(tag.style, attr[i]);
} else {
Object.assign(tag.style, {});
}
} else {
if (attr[i]) {
tag.setAttribute(i, attr[i]);
} else {
tag.removeAttribute(i);
}
}
}
}
let num = 0;
function diff(dom1, dom2) {
let patchs = {}; //用于记录那个属性被改变
let index = 0; //用于记录第几次diff
compiner(dom1, dom2, patchs, index);
return patchs;
}
// 判读字节点是否为文本的函数
function isString(node) {
return Object.prototype.toString.call(node) === "[object String]";
}
// 对props进行diff算法
function diffProps(oldNode, newNode) {
const ptach = {};
/*
先遍历旧的props 若旧props的第n项不同于新props第n项 取新的props第n项key value
先遍历新的props 若旧props没有新props里的属性 取新的props增添的key value
其实应该为props排个序,我这里一开始没太注意,所以忘了
*/
for (let key in oldNode) {
// 这里转化为json字符串是为了比较style,因为style的值为一个对象
// {}!=={}永远成立,所以就算style相同也会判断为不同,需要转化一下
if (JSON.stringify(newNode[key]) !== JSON.stringify(oldNode[key])) {
ptach[key] = newNode[key];
}
}
for (let key in newNode) {
if (!oldNode.hasOwnProperty(key)) {
ptach[key] = newNode[key];
}
}
return ptach;
}
/*
对children进行diff算法比较 这里利用递归对children的每一项进行初始type比较
判断旧children与新children的长度 若旧dom长则对旧dom进行遍历 这里可以看compiner函数
*/
function diffChildren(oldChildren, newChildren, patchs) {
const oldLen = oldChildren && oldChildren.length;
const newLen = newChildren && newChildren.length;
if (oldLen > newLen || oldLen === newLen) {
oldChildren.forEach((item, index) => {
compiner(item, newChildren[index], patchs, ++num);
});
} else {
newChildren.forEach((item, index) => {
if(oldChildren[index]){
compiner(oldChildren[index], item, patchs, ++num);
}else{
compiner(oldChildren[index], item, patchs, num);
}
});
}
}
// diff算法
function compiner(oldNode, newNode, patchs, index) {
let current = [];
// 旧dom不存在 说明是新添了dom
if (!oldNode) {
current.push({ type: "REPLACE", node: newNode });
} else if (!newNode) {
// 新dom不存在 说明删除了dom
current.push({ type: "REMOVE", index });
} else if (isString(oldNode) && isString(newNode)) {
// 新旧dom为文本 比较文本值是否相同 不相同则更换文本
if(oldNode!==newNode){
current.push({ type: "TEXT", text: newNode });
}
} else if (oldNode.type === newNode.type) {
const attr = diffProps(oldNode.props, newNode.props);
Object.keys(attr).length && current.push({ type: "ATTR", attr });
diffChildren(oldNode.children, newNode.children, patchs);
}else{
// 新旧dom的标签不同 则新dom完全替换旧dom
current.push({type:'REPLACE',node:newNode})
}
if (current.length) {
if (Object.keys(patchs).length === index) {
for (let i of current) {
patchs[index].push(i);
}
} else {
patchs[index] = current;
}
}
}
// 创建两个虚拟dom并把dom1渲染成真实dom
const dom1=createElement('div',{className:"div1"},['我是div1',createElement('p',{style:{color:'red'}},['我是p'])])
const dom2=createElement('div',{className:"div2"},['我是div2'
])
const truelyDom1=render(dom1)
document.body.appendChild(truelyDom1)
const diffrance=diff(dom1,dom2)
let count=0;
// 将diff算法后的结果与旧的真实dom进行比对
walkPach(truelyDom1,diffrance)
function walkPach(dom){
const current=diffrance[count++];
const childNodes=dom.childNodes
childNodes && childNodes.forEach((item,index)=>{
walkPach(item)
})
if(current){
doPach(dom,current)
}
}
function doPach(node,current){
const parentNode=node.parentNode
// 四种情况四种不同的操作
current.forEach((item,index)=>{
if(item.type==='TEXT'){
node.textContent=item.text
}
else if(item.type==='ATTR'){
setAttr(node,item.attr)
}
else if(item.type==='REPLACE'){
index===0 && parentNode.replaceChild(item.node,node)
index!==0 && parentNode.appendChild(render(item.node))
}else if(item.type==='REMOVE'){
parentNode.removeChild(node)
}
})
}
目前为止并不完善,今后会一步一步将之修改完美
gitHub地址:github.com/mayu888/Rea…