问题:
图灵完备的语言:简单来说就是能实现任何数学逻辑的语言。
对浏览器来说,dom操作比执行js更加耗费性能.所以使用vdom来模拟dom结构,再用diff算法来比较出dom的变化,最后只渲染变动的部分。要比把大块的dom树结构整个重新渲染,高效的多。所以vdom就有了实际价值和意义。
vdom之前,我们直接使用jQuery操作dom,但有很多问题:
我们说的vdom不是特指,是统称一类技术实现,有很多不同的库来实现。
<ul id="list">
<li class="item">item1li>
<li class="item">item2li>
ul>
// html对应的vdom结构
{
tag: 'ul',
attrs: { id: 'list' },
children: [
{
tag: 'li',
attrs: { className: 'item' },
children: ['item1']
},
{
tag: 'li',
attrs: { className: 'item' },
children: ['item2']
}
]
}
// 使用 snabbdom 的 h 函数来生成上边这个vdom
let vnode = h('ul#list', {}, [
h('li.item', {}, ['item1']),
h('li.item', {}, ['item2']),
]);
// 由此可以看出: h 函数用来生成虚拟节点vnode,他的参数为
h('选择器字符串', '描述对象', '子元素数组或文本字符串')
// patch 函数
// 第一次渲染时,把vnode塞入container
// 参数:空的容器节点, 要在其中渲染的虚拟dom
patch(container, vnode)
// 第二次渲染时,对比两个vnode,只更新必要的部分
// 参数:旧的vnode,新的vnode
patch(vnode, newVnode)
Linux中就有一个基本的diff命令
diff 1.txt 2.txt
来比较两个文件的内容。git中也有diff,来比较改动。前端vue,react中的diff并非独创。
diff算法复杂,实现难度大,源码量大。这里我们去繁就简,讲明白核心流程,不关心细节。但即便如此,依然不简单。
vdom为何使用diff算法:dom操作昂贵,尽量减少;用diff算法找出不同,只更新必要的节点
上节讲了snabbdom库的patch函数,他有两种用法
patch(container, vnode)
这种用法,我们把重点放在如何把表示vdom的js对象变成html;
// 将vdom转化为真实的dom
// 此处只用伪代码写明思路
function createElement(vnode) {
var tag = vnode.tag;
var attrs = vnode.attrs || {};
var children = vnode.children || [];
if(!tag) return null
// 创建真实的dom元素
var elem = document.createElement(tag);
// 设置属性
for(var attrName in attrs) {
if(attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName])
}
}
// 递归生成并插入子元素
children.forEach(function(child) {
elem.appendChild(createElement(child));
});
// 返回真实的dom元素
return elem
}
patch(vnode, newVnode)
这种用法,我们把重点放在如何比较两个vdom的不同
// 比较两个vdom的区别
// 此处只用伪代码写明思路
// 实际上根元素是不变的 如vue,react中的:
// 所以只要比较子元素
function updateChildren(vnode, newVnode) {
var children = vnode.children || [];
var newChildren = newVnode.children || [];
// 遍历子元素
children.forEach(function(childVnode, index) {
var newChildVnode = newChildren[index];
// 新旧比较tag是否相同
if(childVnode.tag === newChildVnode.tag) {
// tag相同则递归对比下一层子元素
updateChildren(childVnode, newChildVnode);
} else {
// tag不同则直接替换该tag
replaceNode(childVnode, newChildVnode)
}
})
}
// 替换节点
function replaceNode(vnode, newVnode) {
var elem = vnode.elem; // 通过现有vnode对应找到真实的dom节点
var newElem = createElement(newVnode);
// 替换
}
上边只是大概讲了核心思路,还有其他更多更复杂的内容