DOM操作是昂贵的,为了减少DOM操作,才有了Virtual DOM。而Virtual DOM的关键就是通过对比新旧vnode,找出差异部分来更新节点。对比的关键算法就是Diff算法。
历史由来:
diff算法历史悠久,并不是虚拟dom提出来的。早在linux系统中,就有diff命令,用于比较两个文本的差异,还有一个最常用的就是git diff命令,由于比较两个版本之间的差异。Virtual DOM的算法是用来对比新旧虚拟dom的差异。
核心逻辑:
1. creatElement
首先先来分析patch函数的第一种用法:patch(container, vnode).。如何把虚拟dom转化成真正的DOM?
假设每个节点都包含tag,props,children三个属性(按最简单的情况考虑)
step 1: 创建节点;
step 2: 设置属性;
step 3; 遍历子节点,子节点也需要结果step1,2,然后加到当前节点中。
经过分析,我们发现创键DOM,需要用到递归。
代码模拟如下:
function createElement(vnode) { var tag = vnode.tag;//获取元素的标签 var props = vnode.props || {};//获取元素的属性 var children = vnode.children || [];//获取子节点 if (!tag) { return null; } var ele = document.createElement(tag);//step1:创建元素 // step2:设置属性 var attrName; for (attrName in props) { if (props.hasOwnProperty(attrName)) { ele.setAttribute(attrName, props[attrName]) } } //step3:深度遍历子元素,并插入当前节点 children.forEach(element => { ele.appendChild(createElement(element)); //递归调用 }); return ele; }
2. updateChild
接着再来分析一下patch函数的第二种用法:patch(oldVnode, newVnode)。如何找出新旧节点的差异,并更新节点?
假设只考虑最简单的情况,只改变以下<li>标签的值。
function updateChild(vnode,newVnode) { var oldchild = vnode.children || []; var newchild = newVnode.children || []; oldchild.forEach(function (childItem, index) { var newChildItem = newchild[index]; //假设层级相同 if(newChildItem.tag === childItem.tag) { updateChild(childItem, newChildItem) //如果相同则递归遍历 } else { replaceNode(childItem, newChildItem)//否则替换dom } }) } function replaceNode(parantNode, oldchildNode, newChildNode) { var oldelem = oldchildNode.elem; //获取真实dom var newElem = newChildNode.elem; //替换dom parantNode.replaceChild(newElem,oldelem); }
注:以上代码都是伪代码,只考虑了最简单的情况。
总结:
实际diff算法非常复杂,还需考虑节点的新增,删除;节点的重新排序;节点属性、样式、事件的绑定等。
1. virtual dom是什么?为何使用VD?
虚拟DOM
用JS模拟DOM结构
DOM操作非常昂贵
将DOM对比放在JS层,提高效率
2. 核心API
h('<html 标签名>',{属性},[children])//含有子节点的
h('<html 标签名>',{属性},'text'])//没有子节点,只有文本,如<p>this is VN</p>
patch(container, vnode)//初次渲染
patch(oldVnode, newVnode); //re-render
3. diff算法
creatElement,updateChild逻辑