上一篇文章,我說到了如何實現一個簡單的虛擬DOM,這一篇文章是接着上一篇文章的知識點的。node
咱們都知道虛擬DOM其實就是JS對象,咱們用JS來操做對象比操做DOM性能要好得多。算法
let patches = {};
let index = 0;
複製代碼
因此咱們須要創建四個標識符。bash
const ATTR = 0; // 屬性
const TEXT = 1; // 文本
const REMOVE = 2; // 刪除
const REPLACE = 3; // 替換
複製代碼
let vDom1 = createElement("div", {class: "div"}, [
createElement("div", {class: "div"}, ["a"])
]);
let vDom2 = createElement("div", {class: "div1"}, [
createElement("div", {class: "div2"}, ["b"])
]);
複製代碼
diff(vDom1, vDom2)
function diff(oldTree, newTree){
walk(oldTree, newTree, index); // 遍歷兩個虛擬DOM樹
}
複製代碼
// 我這裏使用了不是很準確的比較(可使用toString.call)
// 若是都是文本
// patch是補丁對象
// 數據描述: {type: 不一樣的類型,不一樣的地方}
if((typeof oldNode === "string") && (typeof newNode === "string")){
// 若是文本內容不同
if(newNode !== oldNode){
patch.push({type: TEXT, text: newNode});
}
}
複製代碼
// 若是類型相同就比較屬性, 類型不相同默認換掉了整個元素
if(oldNode.type === newNode.type){
// 遍歷新老節點屬性的不一樣
let attr = diffAttr(oldNode.props, newNode.props);
// 若是有不一樣, 就加入patch中
if (Object.keys(attr).length > 0) {
patch.push({ type: ATTR, attr });
}
// 遍歷子節點
diffChildren(oldNode.children, newNode.children);
}
複製代碼
遍歷屬性數據結構
function diffAttr(oldAttr, newAttr){
let attr = {};
// 看兩個屬性是否不一樣(修改)
for (key in oldAttr) {
if(oldAttr[key] != newAttr[key]){
attr[key] = newAttr[key];
}
}
// 是否新增
for (key in newAttr) {
if(!oldAttr.hasOwnProperty(key)){
attr[key] = newAttr[key];
}
}
return attr;
}
複製代碼
遍歷子節點的屬性dom
function diffChildren(oldChildren, newChildren){
oldChildren.forEach(function(child, i){
// 子節點遞歸遍歷屬性
walk(child, newChildren[i], ++ index);
});
}
複製代碼
// 若是沒有新節點,說明刪除了,標記處刪除的索引
if(!newNode){
patch.push({type: REMOVE, index});
}
複製代碼
// 其他狀況爲替換
patch.push({type: REPLACE, newNode});
複製代碼
let patches = {};
let index = 0;
const ATTR = 0;
const TEXT = 1;
const REMOVE = 2;
const REPLACE = 3;
function diff(oldTree, newTree){
walk(oldTree, newTree, index);
}
function walk(oldNode, newNode, index){
let patch = [];
// 刪除
if(!newNode){
patch.push({type: REMOVE, index});
// 文本
}else if((typeof oldNode === "string") && (typeof newNode === "string")){
if(newNode !== oldNode){
patch.push({type: TEXT, text: newNode});
}
}else if(oldNode.type === newNode.type){
// 屬性
let attr = diffAttr(oldNode.props, newNode.props);
if (Object.keys(attr).length > 0) {
patch.push({ type: ATTR, attr });
}
diffChildren(oldNode.children, newNode.children);
}else {
// 替換
patch.push({type: REPLACE, newNode});
}
if(patch.length > 0){
patches[index] = patch;
}
}
// 比較屬性的不一樣
function diffAttr(oldAttr, newAttr){
let attr = {};
// 看兩個屬性是否不一樣(修改)
for (key in oldAttr) {
if(oldAttr[key] != newAttr[key]){
attr[key] = newAttr[key];
}
}
// 是否新增
for (key in newAttr) {
if(!oldAttr.hasOwnProperty(key)){
attr[key] = newAttr[key];
}
}
return attr;
}
// 比較子節點的屬性
function diffChildren(oldChildren, newChildren){
oldChildren.forEach(function(child, i){
walk(child, newChildren[i], ++ index);
});
}
複製代碼
咱們查看一下補丁對象 post
首先創建一個索引對象let patchIndex = 0;
性能
patch(dom, patches)
function patch(dom, patches){
walkPatch(dom);
}
複製代碼
遍歷補丁的實現ui
function walkPatch(dom){
// 獲取當前節點的補丁
let patch = patches[patchIndex ++];
// 獲取子節點
let children = dom.childNodes;
// 遍歷子節點
// 遍歷到最後一個元素,從後往前打補丁
children.forEach((child)=>walkPatch(child));
// 若是有補丁,就打補丁
if(patch){
doPatch(dom, patch);
}
}
複製代碼
// 遍歷屬性
// key 就是 class或者value(這個value是屬性)
// value 就是 類名或者是值
for (key in p.attr) {
let value = p.attr[key];
// 若是有值(其實就是上一篇虛擬DOM中的設置屬性)
if(value){
if(key === "value"){
if(node.type.toUpperCase() === "INPUT" || node.type.toUpperCase() === "TEXTAREA"){
node.value = value;
}
}else {
node.setAttribute(key, value);
}
// 沒有值,就是刪除屬性
}else {
node.removeAttribute(key);
}
}
複製代碼
// 替換文本節點
node.textContent = p.text;
複製代碼
// 刪除本身
node.parentNode.removeChild(node);
複製代碼
let { newNode } = p;
// 若是是元素就建立元素不然就是文本
newNode = (newNode instanceof Element) ? createDom(newNode): document.createTextNode(newNode);
// 用新節點替換舊結點
newNode.parentNode.replaceChild(newNode, node);
複製代碼
總體代碼spa
function doPatch(node, patch){
patch.forEach((p)=>{
switch (p.type) {
case ATTR:
// 遍歷屬性
for (key in p.attr) {
let value = p.attr[key];
// 若是有值(其實就是上一篇虛擬DOM中的設置屬性)
if(value){
if(key === "value"){
if(node.type.toUpperCase() === "INPUT" || node.type.toUpperCase() === "TEXTAREA"){
node.value = value;
}
}else {
node.setAttribute(key, value);
}
// 沒有值,就是刪除屬性
}else {
node.removeAttribute(key);
}
}
break;
case TEXT:
// 替換文本節點
node.textContent = p.text;
break;
case REMOVE:
// 刪除本身
node.parentNode.removeChild(node);
break;
case REPLACE:
let { newNode } = p;
// 若是是元素就建立元素不然就是文本
newNode = (newNode instanceof Element) ? createDom(newNode): document.createTextNode(newNode);
// 用新節點替換舊結點
newNode.parentNode.replaceChild(newNode, node);
break;
default:
break;
}
})
}
複製代碼
未打補丁的DOM樹 3d
let vDom1 = createElement("div", {class: "div"}, [
createElement("div", {class: "div"}, ["a"]),
createElement("div", {}, ["b"]),
createElement("div", {class: "div"}, [
createElement("div", {class: "div"}, ["c"]),
createElement("div", {class: "div"}, ["d"])
])
]);
let vDom2 = createElement("div", {class: "div1"}, [
createElement("div", {class: "div2"}, ["1"]),
createElement("div", {class: "div3"}, ["2"]),
createElement("div", {}, [
createElement("div", {class: "div5"}, ["3"]),
createElement("div", {class: "div6"}, ["4"])
])
]);
複製代碼
打補丁之前的DOM樹