参考代码将上传至我的github仓库,欢迎互粉:https://github.com/dashnowords/blogs/tree/master
一. 再谈从Virtual-Dom生成真实DOM在上一篇博文《javascript基础修炼(10)——VirtualDOM和基本DFS》中第三节演示了关于如何利用Virtual-DOM的树结构生成真实DOM的部分,原本希望让不熟悉深度优先算遍历的读者先关注和感受一下遍历的基本流程,所以演示用的DOM节点只包含了类名和文本内容,结构简单,在复现DOM结构时直接拼接字符串在控制台显示出来的方式。许多读者留言表示对如何从Virtual-Dom得到真实的DOM节点仍然很困惑。
所以本节会先为Element类增加渲染方法,演示如何将Virtual-Dom转换为真正的DOM节点并渲染在页面上。
element.js示例代码:
//Virtual-DOM 节点类定义 class Element{ /** * @param {String} tag 'div' 标签名 * @param {Object} props { class: 'item' } 属性集 * @param {Array} children [ Element1, 'text'] 子元素集 * @param {String} key option */ constructor(tag, props, children, key) { this.tag = tag; this.props = props; if (Array.isArray(children)) { this.children = children; } else if (typeof children === 'string'){ this.children = null; this.key = children; } if (key) {this.key = key}; } /** * 从虚拟DOM生成真实DOM * @return {[type]} [description] */ render(){ //生成标签 let el = document.createElement(this.tag); let props = this.props; //添加属性 for(let attr of Object.keys(props)){ el.setAttribute(attr, props[attr]); } //处理子元素 var children = this.children || []; children.forEach(function (child) { var childEl = (child instanceof Element) ? child.render()//如果子节点是元素,则递归构建 : document.createTextNode(child);//如果是文本则生成文本节点 el.appendChild(childEl); }); //将DOM节点的引用挂载至对象上用于后续更新DOM this.el = el; //返回生成的真实DOM节点 return el; } } //提供一个简写的工厂函数 function h(tag, props, children, key) { return new Element(tag, props, children, key); }测试一下定义的Element类:
var app = document.getElementById('anchor'); var tree = h('div',{class:'main', id:'body'},[ h('div',{class:'sideBar'},[ h('ul',{class:'sideBarContainer',cprop:1},[ h('li',{class:'sideBarItem'},['page1']), h('li',{class:'sideBarItem'},['page2']), h('li',{class:'sideBarItem'},['page3']), ]) ]), h('div',{class:'mainContent'},[ h('div',{class:'header'},['header zone']), h('div',{class:'coreContent'},[ h('div',{fx:1},['flex1']), h('div',{fx:2},['flex2']) ]), h('div',{class:'footer'},['footer zone']), ]) ]); //生成离线DOM var realDOM = tree.render(); //挂载DOM app.appendChild(realDOM);这次不用再看控制台了,虚拟DOM的内容已经变成真实的DOM节点渲染在页面上了。
接下来,就正式进入通过DOM-Diff来检测Virtual-DOM的变化以及更新视图的后续步骤。
二. DOM-Diff的目的在经历了一些操作或其他影响后,Virtual-DOM上的一些节点发生了变化,此时页面上的真实DOM节点是与旧的DOM树保持一致的(因为旧的DOM树就是依据旧的Virtual-DOM来渲染的),DOM-Diff所实现的功能就是找出新旧两棵Virtual-DOM之间的区别,并将这些变更渲染到真实的DOM节点上去。
三. DOM-Diff的基本算法描述为了提升效率,需要在算法中使用基本的“批处理”思维,也就是说,先通过遍历Virtual-DOM找出所有节点的差异,将其记录在一个补丁包patches中,遍历结束后再根据补丁包一并执行addPatch()逻辑来更新视图。完整的树比较算法时间复杂度过高,DOM-Diff中使用的算法是只对新旧两棵树中的节点进行同层比较,忽略跨层比较。
历,并为每个节点添加索引
新旧节点的tagName或者key不同
表示旧的节点需要被替换,其子节点也就不需要遍历了,这种情况的处理比较简单粗暴,打补丁阶段会直接把整个旧节点替换成新节点。
新旧节点tagName和key相同
开始检查属性:
检查属性删除的情况
检查属性修改的情况
检查属性新增的情况
将变更以属性变更的类型标记加入patches补丁包中
完成比较后根据patches补丁包将Virtual-DOM的变化渲染到真实DOM节点。
四. DOM-Diff的简单实现 4.1 期望效果