javascript基础修炼(11)——DOM-DIFF的实现 (3)

javascript基础修炼(11)——DOM-DIFF的实现

4.3 根据补丁包更新视图

拿到补丁包后,就可以更新视图了,更新视图的算法逻辑如下:

再次深度优先遍历Virtual-DOM,如果遇到有补丁的节点就调用changeDOM( )方法来修改页面,否则增加索引继续搜索。

addPatch.js:

/** * 根据补丁包更新视图 */ function addPatch(oldTree, patches) { let globalIndex = 0; //遍历时为节点添加索引,方便打补丁时找到节点 dfsPatch(oldTree, patches, globalIndex);//patches会以传址的形式进行递归,所以不需要返回值 } //深度遍历节点打补丁 function dfsPatch(oldNode, patches, index) { let nextIndex = index + 1; //如果有补丁则打补丁 if (patches[index] !== undefined) { //刷新当前虚拟节点对应的DOM changeDOM(oldNode.el,patches[index]); } //如果有自子节点且子节点是Element实例则递归遍历 if (oldNode.children.length && oldNode.children[0] instanceof Element) { for(let i =0 ; i< oldNode.children.length; i++){ nextIndex = dfsPatch(oldNode.children[i], patches, nextIndex); } } return nextIndex; } //依据补丁类型修改DOM function changeDOM(el, patches) { patches.forEach(function (patch, index) { switch(patch.type){ //改变属性 case 'ChangeProps': patch.props.forEach(function (prop, index) { switch(prop.type){ case 'NEW': case 'MOD': el.setAttribute(prop.propName, prop.value); break; case 'DEL': el.removeAttribute(prop.propName); break; } }) break; //改变文本节点内容 case 'ChangeInnerText': el.innerHTML = patch.value; break; //替换DOM节点 case 'Replace': let newel = h(patch.node.tag, patch.node.props, patch.node.children).render(); el.parentNode.replaceChild(newel , el); } }) }

在页面测试按钮的事件监听函数中,DOM-Diff执行后,再调用addPatch( )即可看到,新的DOM树已经被渲染至页面了:

javascript基础修炼(11)——DOM-DIFF的实现

本思想其实并不是特别难理解,自己手写代码时主要的难点出现在节点索引的追踪上,因为在addPatch( )阶段,需要将补丁包中的节点索引编号与旧的Virtual-DOM树对应起来,这里涉及的基础知识点有两个:

函数形参为对象类型时是传入对象引用的,在函数中修改对象属性是会影响到函数外部作用域的,而patches补丁包正是利用了这个基本特性,从顶层向下传递在最外层生成的patches对象引用,深度优先遍历时用于递归的函数有一个形参表示patches,这样在遍历时,无论遍历到哪一层,都是共享同一个patches的。

第二个难点在于节点索引追踪,比如第二层有3个节点,第一个被标号为2,同层第二个节点的编号取决于第一个节点的子节点消耗了多少个编号,所以代码中在dfswalk( )迭代函数中return了一个编号,向父级调用者传递的信息是:我和我所有的子级节点都已经遍历完了,最后一个节点(或者下一个可使用节点)的索引是XXX,这样遍历函数能够正确地标记和追踪节点的索引了,觉得这一部分不太好理解的读者可以自己手画一下深度优先遍历的过程就比较容易理解了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgfyx.html