初探虚拟 DOM
栏目: JavaScript · 发布时间: 6年前
内容简介:如果有这么一张表格要你维护。后续涉及到表的增删改,你会怎么做?表格简单的时候还好,用 JavaScript 操作起来还算方便。但随着应用越来越复杂,需要处理的数据也越来越大,越来越复杂的时候,需要利用 JavaScript 操作的地方也会越来越多,这个时候准确地修改数据就变得不是那么容易了。
如果有这么一张表格要你维护。
后续涉及到表的增删改,你会怎么做?
- 增:先找到正确的位置,再插元素进去?
- 删:找到正确的元素,删掉它?
- 改:找到正确的元素,修改它?
表格简单的时候还好,用 JavaScript 操作起来还算方便。但随着应用越来越复杂,需要处理的数据也越来越大,越来越复杂的时候,需要利用 JavaScript 操作的地方也会越来越多,这个时候准确地修改数据就变得不是那么容易了。
虚拟 DOM 的产生
针对前面的情况,那么能不能用一个东西来存储页面的视图状态,当视图状态发送变化时,读取这个东西,然后更新页面?
比如这一段 HTML 代码对应的 DOM,
<div> <div> <span>hello</span> </div> <span>world</span> </div> 复制代码
我们用另外的一个对象来表示它
let nodesData = { tag: 'div', children: [ { tag: 'div', children: [ { tag: 'span', children: [ { tag: '#text', text: 'hello' } ] } ] }, { tag: 'span', children: [ { tag: '#text', text: 'world' } ] } ] } 复制代码
用这个对象来表示 DOM 结构,我们可以根据这个对象来构建真正的 DOM。
现在我们需要写一个函数,将这个虚假的 DOM 转化为真实的 DOM。
化假为真
function vNode({tag, children, text}){ this.tag = tag this.children = children this.text = text } vNode.prototype.render = function(){ if(this.tag === '#text'){ return document.createTextNode(this.text) } let el = document.createElement(this.tag) this.children.map((vChild) => { el.appendChild(new vNode(vChild).render()) }) return el } 复制代码
调用上面的这个函数可以将我们用来表示 DOM 的对象(虚假 DOM)变成真正的 DOM。
let node = new vNode(nodesData) node.render() 复制代码
这样,就化假 DOM 为真 DOM 了。
当我们的需要改变 DOM 时,只需要改变其对应的虚假 DOM,再调用一下 render 函数,就可以改变真实 DOM,不需要我们亲自用 JavaScript 去操作页面中的 DOM。
局部更新
上面虽然实现了从虚假 DOM 到真实 DOM 的转化,但是也有一个问题,那就是每次转化都会遍历所有的 DOM 结构,通通的全部转化一遍。如果只有一个小地方发生了改变,也需要将全部的 DOM 更新一遍,那这样就太耗费性能了,我们应该比较虚假 DOM 的变化,只更新变化的地方。
function patchElement(parent, newVNode, oldVNode, index = 0) { if (!oldVNode) { parent.appendChild(newVNode.render()); } else if (!newVNode) { parent.removeChild(parent.childNodes[index]); } else if (newVNode.tag !== oldVNode.tag || newVNode.text !== oldVNode.text) { parent.replaceChild(new vNode(newVNode).render(), parent.childNodes[index]); } else { for ( let i = 0; i < newVNode.children.length || i < oldVNode.children.length; i++ ) { patchElement( parent.childNodes[index], newVNode.children[i], oldVNode.children[i], i ); } } } 复制代码
通过这个算法,逐层比较新旧虚假 DOM 的结构变化,如果没变,就继续往下遍历;如果发现结构发生了变化,就重新生成真实 DOM 替换掉旧的。
来看一看效果。
从图中可以看到,当虚假 DOM 发生变化时,在更新真实 DOM 的过程中,只更新了发生了变化的那一部分,没有发生变化的地方是没动的,这样就优化了性能。
结语
这是一个非常粗糙的实现,diff 算法非常简单地比较了差异,这里仅仅表达了一下虚拟 DOM 的实现思想,在实际运用过程还有很多地方需要考虑。
这里贴个完整代码。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <div id="test"></div> <script> let nodesData = { tag: 'div', children: [ { tag: 'div', children: [ { tag: 'span', children: [ { tag: '#text', text: 'hello' } ] } ] }, { tag: 'span', children: [ { tag: '#text', text: 'world' } ] } ] }; let nodesData2 = { tag: 'div', children: [ { tag: 'div', children: [ { tag: 'span', children: [ { tag: '#text', text: 'HELLO' } ] } ] }, { tag: 'span', children: [ { tag: '#text', text: 'WORLD' } ] } ] }; function vNode({ tag, children, text }) { this.tag = tag; this.children = children; this.text = text; } vNode.prototype.render = function () { if (this.tag === '#text') { return document.createTextNode(this.text); } let el = document.createElement(this.tag); this.children.map(vChild => { el.appendChild(new vNode(vChild).render()); }); return el; }; function patchElement(parent, newVNode, oldVNode, index = 0) { if (!oldVNode) { parent.appendChild(newVNode.render()); } else if (!newVNode) { parent.removeChild(parent.childNodes[index]); } else if (newVNode.tag !== oldVNode.tag || newVNode.text !== oldVNode.text) { parent.replaceChild(new vNode(newVNode).render(), parent.childNodes[index]); } else { for ( let i = 0; i < newVNode.children.length || i < oldVNode.children.length; i++ ) { patchElement( parent.childNodes[index], newVNode.children[i], oldVNode.children[i], i ); } } } let node1 = new vNode(nodesData); let node2 = new vNode(nodesData2); let test = document.querySelector('#test'); test.appendChild(node1.render()); setTimeout(() => { patchElement(test, node2, node1, 0); }, 5000); </script> </body> </html> 复制代码
以上所述就是小编给大家介绍的《初探虚拟 DOM》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective Ruby:改善Ruby程序的48条建议
Peter J. Jones / 杨政权、秦五一、孟樊超 / 机械工业出版社 / 2016-1 / 49
如果你是经验丰富的Rub程序员,本书能帮助你发挥Ruby的全部力量来编写更稳健、高效、可维护和易执行的代码。Peter J.Jones凭借其近十年的Ruby开发经验,总结出48条Ruby的最佳实践、专家建议和捷径,并辅以可执行的代码实例。 Jones在Ruby开发的每个主要领域都给出了实用的建议,从模块、内存到元编程。他对鲜为人知的Ruby方言、怪癖、误区和强力影响代码行为与性能的复杂性的揭......一起来看看 《Effective Ruby:改善Ruby程序的48条建议》 这本书的介绍吧!