DOM与虚拟DOM
前段时间在学react,刚接触DOM被搞得迷迷糊糊的,为此还和实验室的老师争论了一番,也参考了一些资料,记录一下自己的理解吧。
一、vdom是什么
vdom是虚拟DOM(Virtual DOM)的简称,指的是用JS模拟的DOM结构,将DOM变化的对比放在JS层来做。换句话说,vdom就是JS对象。
举个栗子,下面的DOM结构:
<ul id="list">
<li class="item">Item1</li>
<li class="item">Item2</li>
</ul>
映射为虚拟DOM是这样式儿的:
{
tag: "ul",
attrs: {
id: "list"
},
children: [
{
tag: "li",
attrs: { className: "item" },
children: ["Item1"]
},
{
tag: "li",
attrs: { className: "item" },
children: ["Item2"]
}
]
}
二、为什么要用vdom
比如说现在要记录同学们的成绩(LXL好惨啊)
[
{
name: "赵大哥",
class: "1",
score: "100"
},
{
name: "小窦",
class: "2",
score: "100"
},
{
name: "LXL",
class: "3",
score: "59"
},
{
name: "Ms_Xiang",
class: "4",
score: "100"
},
]
想要实现修改信息时表格跟着修改,用jQuery实现如下:
<!DOCTYPE html>
<html lang = 'en'>
<head>
<meta charset="UTF-8">
<title>Table</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">Change here</button>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
const data = [ {
name: "赵大哥",
class: "1",
score: "100"
},
{
name: "小窦",
class: "2",
score: "100"
},
{
name: "LXL",
class: "3",
score: "59"
},
{
name: "Ms_Xiang",
class: "4",
score: "100"
},
];
//渲染函数
function render(data) {
const $container = $('#container');
$container.html('');
const $table = $('<table>');
// 重绘一次
$table.append($('<tr><td>name</td><td>class</td><td>score</td></tr>'))
data.forEach(item => {
//每次进入都重绘
$table.append($(`<tr><td>${item.name}</td><td>${item.class}</td><td>${item.score}</td></tr>`))
})
$container.append($table);
}
$('#btn-change').click(function () {
data[2].class = 1;
data[2].score = parseInt(Math.random()*40+61);
render(data);
});
</script>
</body>
</html>
果然,LXL换了班以后就能及格了
这样每次点击按钮,都会有相应的视图的变化,但是审查一下元素,每次改动之后,不管当前数据是否和以前数据一样,table标签都要重新渲染一次,而DOM重绘会消耗浏览器的性能。
所以,如果采用JS对象模拟的办法,将DOM的对比操作放在JS层,减少浏览器不必要的重绘,就可以提高效率了。
当然不排除极端情况,表格中的每个数据都需要重绘,这时候虚拟DOM就当场翻车了,它甚至比DOM多了一层diff算法,所以在这种情况下虚拟DOM并不比DOM快。
但是,虚拟DOM还是有自身独特的优势的:
1、它打开了函数式的UI编程的大门,即UI = f(data)这种构建UI的方式。
2、可以将JS对象渲染到浏览器DOM以外的环境中,也就是支持了跨平台开发,比如ReactNative。
三、使用snabbdom实现vdom
snabbdom地址在这https://link.zhihu.com/?target=https%3A//github.com/snabbdom/snabbdom
这是一个简易的实现vdom功能的库,相对于vue 和 react,这个库更适合研究vdom(更简易)。vdom有两个核心的api:h函数和patch函数,h函数用来生成vdom对象,patch函数用来做与虚拟DOM的对比和将vdom挂载到真实的DOM上。
用snabbdom重写一下刚才的分数表:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>table</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">Change here</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script>
let snabbdom = window.snabbdom;
// 定义patch
let patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
]);
//定义h
let h = snabbdom.h;
const data = [ {
name: "赵大哥",
class: "1",
score: "100"
},
{
name: "小窦",
class: "2",
score: "100"
},
{
name: "LXL",
class: "3",
score: "59"
},
{
name: "Ms_Xiang",
class: "4",
score: "100"
},
];
data.unshift({name: "name", class: "class", score: "score"});
let container = document.getElementById('container');
let vnode;
const render = (data) => {
let newVnode = h('table', {}, data.map(item => {
let tds = [];
for(let i in item) {
if(item.hasOwnProperty(i)) {
tds.push(h('td', {}, item[i] + ''));
}
}
return h('tr', {}, tds);
}));
if(vnode) {
patch(vnode, newVnode);
} else {
patch(container, newVnode);
}
vnode = newVnode;
}
render(data);
let btnChnage = document.getElementById('btn-change');
btnChnage.addEventListener('click', function() {
data[3].class = 1;
data[3].score = parseInt(Math.random()*40+61);
render(data);
})
</script>
</body>
</html>
发现只有被更改的地方发生了变化 ,这样就节省了浏览器的开销。
四、diff算法
1.什么是diff算法
diff算法,即比较两段文本之间差异的一种算法,其实这个算法在Linux的diff命令中已有体现,并且我们常用的git-diff算法也是它。
2.vdom为啥要用diff算法
因为所以,科学道理。 DOM的操作是非常昂贵的,因此我们要尽可能的减少DOM操作,每次只需要找出本次DOM操作必须更新的节点去更新,其他的不更新,这个过程就需要用到diff算法。
3.vdom中diff算法的简易实现
将vdom转化为真实dom:
const createElement = (vnode) => {
let tag = vnode.tag;
let attrs = vnode.attrs || {};
let children = vnode.children || [];
if(!tag) {
return null;
}
//创建元素
let elem = document.createElement(tag);
//属性
let attrName;
for (attrName in attrs) {
if(attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName]);
}
}
//子元素
children.forEach(childVnode => {
//给elem添加子元素
elem.appendChild(createElement(childVnode));
})
//返回真实的dom元素
return elem;
}
用简易diff算法做更新操作:
function updateChildren(vnode, newVnode) {
let children = vnode.children || [];
let newChildren = newVnode.children || [];
children.forEach((childVnode, index) => {
let newChildVNode = newChildren[index];
if(childVnode.tag === newChildVNode.tag) {
//深层次对比, 递归过程
updateChildren(childVnode, newChildVNode);
} else {
//替换
replaceNode(childVnode, newChildVNode);
}
})
}
嗯,差不多了,收工hhh~