因为今天在给一个项目做一个小demo,用到了jQuery,想起用了VUE之后就没怎么用过jQuery做DOM操作了;于是感慨到virtual DOM的好处,于是想着回家整理整理
什么是virtual DOM ?为什么存在virtual DOM?
- 用JS模拟虚拟的DOM结构,生成虚拟的DOM,当数据更新是,对比DOM的变化,只更新需要更新的数据,从而减少"昂贵"的DOM操作;
- 为了提高DOM重绘制的性能
HTML代码如下:
对应的vdom如下:(class是关键字,所以会别名叫做className)
vdom如何应用,核心API是什么?
使用snabbdom 来实现vdom==>dom
-
使用snabbdom ,把一个创建好的虚拟DOM生成为浏览器页面上的真实DOM;
-
snabbdom有以下重要的API
h(‘<标签名>’, {…属性…}, […子元素…])
h(‘<标签名>’, {…属性…}, ‘….’)
patch(container, vnode)
patch(vnode, newVnode)
-
创建一个div和一个button按钮,div放入一个虚拟dom生成的UI li 列表,点击button按钮,实现数据的diff对比更新
// html
<div id="container"></div>
<button id="changeNode">change</button>
// 全局注册
var snabbdom = window.snabbdom;
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
]);
// 定义h函数
var h = snabbdom.h
// 生成VNODE
var vnode = h('ui#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 2'),
])
// 获取容器
var container = document.getElementById('container');
// 第一次把VNODE全部更新到容器中去
patch(container, vnode)
// 准备新节点
var newVnode = h('ui#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item222 2'),
h('li.item', {}, '33232 2'),
])
document.getElementById("changeNode").addEventListener("click", function() {
// 点击按钮,通过diff算法更新节点数据
patch(vnode, newVnode)
})
diff算法
- vdom中找本次DOM需要更新节点来更新
- patch函数,首次渲染,新旧对比,打补丁更新
- patch(container, vnode) 和 patch(vnode, newVnode)
- createElment & updateChildren
createElment 的实现
- 实现虚拟dom节点核心逻辑思想是:创建根节点,如果有子节点,继续递归调用
createElment
方法,只到把节点append进上一级节点。从而在页面上出现真实的DOM结构; - 对应着
snabbdom patch(container, vnode)
伪代码逻辑如下:
function createElement(vnode){
var tag=vnode.tag
var attr=vnode.attrs||{}
var children=vnode.children||[]
if(!tag){
return null;
}
// 真实的DOM元素
var elem=document.createElement(tag);
var attrName
for(attrName in attrs){
elem.setAttribute(attrName,attrs[attrName]);
}
// 子元素
children.forEach(childVnode=>{
// 给Element 添加子元素,递归
elem.appendChild(createElement(childVnode))
});
// 返回创建好的DOM元素
return elem;
}
updateChildren & diff
- diff只更新当前需要更新的数据
- 对比节点的数据,相同则跳过,不相同replace掉久的节点
- 对应着
snabbdom patch(vnode, newVnode)
伪代码逻辑如下:
function updateElement(vnode,newVnode){
var children=vnode.children||[];
var newChildren=newChildren.children||[];
children.forEach((item,index)=>{
var newItem=newChildren[index];
if(item==newItem){
// 递归深层次对比
updateElement(item,newItem);
}else{
// 不同则替换
replaceNode(item,newItem);
}
})
}
React的DIFF算法为什么时间复杂度是O(n)复杂度而不是常规的O(n^3)
时间复杂度的理解见我这篇文章:算法与数据结构 | 时间复杂度分析 / 更准确的描述代码的时间复杂度
为啥呢?
首先:常规的O(n^3);
- 传统的diff需要出了上面的比较之外,还需要跨级比较。
- 两个新旧节点的遍历是O(n^2)
- 执行节点的更新再一次,所以为O(n^3)
React 的O(n)
- react树diff对比是按照层级,会给树编号0,1,2,3,4… 然后相同的编号层级进行比较,所以复杂度是n
diff 算法非常复杂,以上只能算是浅谈了,了解了大概的设计逻辑;
diff还在一些场景用过,就是git 提交的时候常常 git diff XXX 看文件对比哈哈哈哈