虚拟DOM
1. 虚拟DOM 是什么?
虚拟DOM是利用 了js的对象的Object的对象模型来模拟真实DOM, 那么它的结构是一个树形结构
2.为什么要使用虚拟DOM
解答:
虚拟dom对应的是真实dom, 使用document.CreateElement 和 document.CreateTextNode创建的就是真实节点,而真实节点每次都要全部重新渲染,对性能造成巨大的浪费。虚拟DOM判断新DOM与旧DOM差别,得到差异,然后渲染。
虚拟DOM实现步骤
- 用JavaScript对象模拟DOM
- 把此虚拟DOM转成真实DOM并插入页面中
- 如果有事件发生修改了虚拟DOM
- 比较两棵虚拟DOM树的差异,得到差异对象
- 把差异对象应用到真正的DOM树上
diff算法
diff算法是用来比较两个或是多个文件, 返回值是文件的不同点
diff算法是同级比较的
diff思维也是来自后端
diff算法的比较思维
比较后会出现四种情况:
1、此节点是否被移除 -> 添加新的节点
2、属性是否被改变 -> 旧属性改为新属性
3、文本内容被改变-> 旧内容改为新内容
4、节点要被整个替换 -> 结构完全不相同 移除整个替换
diff.js的简单代码实现
let utils = require('./utils');
let keyIndex = 0;
function diff(oldTree, newTree) {
//记录差异的空对象。key就是老节点在原来虚拟DOM树中的序号,值就是一个差异对象数组
let patches = {};
keyIndex = 0; // 儿子要起另外一个标识
let index = 0; // 父亲的表示 1 儿子的标识就是1.1 1.2
walk(oldTree, newTree, index, patches);
return patches;
}
//遍历
function walk(oldNode, newNode, index, patches) {
let currentPatches = [];//这个数组里记录了所有的oldNode的变化
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]) {
attrsPatch[attr] = newAttrs[attr];
}
}
for (let attr in newAttrs) {
if (!oldAttrs.hasOwnProperty(attr)) {
attrsPatch[attr] = newAttrs[attr];
}
}
return attrsPatch;
}
module.exports = diff;
新旧虚拟dom比较的时候,是先同层比较,同层比较完看看时候有儿子,有则需要继续比较下去,直到没有儿子。
同层比较,比较顺序是上面的数字来,把不同的打上标记,放到数组里面去,统一交给patch处理。
patch.js的简单实现
let keyIndex = 0;
let utils = require('./utils');
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;
}
});
}
module.exports = patch;
整个VDOM的使用流程(Vue)
- 创建VDOM树
- 利用render函数渲染页面
- 数据改变,生成新的vDOM
- 通过diff算法比较 新 旧 两个VDOM , 将不同的地方进行修改, 相同的地方就地复用 , 最后在通过render函数渲染页面
注:
这也是vue高性能的原因之一!