去掉虚拟两个字,先了解下什么是DOM?
在原生JS中有DOM操作,就是使用浏览器提供的一些API,选中一个元素对它进行操作,比如说:增加一个属性、增加一个孩子、修改它里面的内容、改变它的位置,直接对DOM操作。这个DOM实际上对应的就是浏览器里面你所能看的见的对应的某个元素。
那什么是虚拟DOM
假设我们要操作一个数据的报表,这个报表大概有几百条数据,我们要对这个报表进行一个排序的操作
我们有这样一个表格,这里面大概有100多项
姓名 |
年纪 |
分数 |
等等 |
小红 |
12 |
44 |
|
小花 |
13 |
78 |
|
当我去点年纪的时候,希望年纪里的所有DOM元素可以按照年纪去排序,当我点新增的时候会在表格下面再新增一行数据,当我点击姓名的时候所有DOM按照姓名去做一个排序,我们可以想下用原生JS怎么去做排序,用JS去对DOM结构去排序,操作起来很难实现,也很麻烦。
后来我们有了MVVM框架可以对数据排序,数据对应到页面上的DOM结构,我们只需要对数据排序,那里面的DOM结构自然就排序了。
我们自己实现一个框架,假设数据变了,那我们的DOM结构也就变了,难道我们需要把数据重新渲染一次,如果用户频繁大量改动数据,DOM也会频繁改动,就会造成卡顿,那我们可以去做一些优化,怎么去做优化呢?
那我们可以像计算机内存那样,我们可以自己设定一个虚拟的数据结构,它是对真实的DOM结构是一一对应的,我们可以先对虚拟的数据结构进行操作,等全部操作完成了,再把它渲染成真实的DOM,那就变成了真实的数据。
假设用户只做了微小的改动,比如增加了2条数据,那我们可以想打补丁一样,只把这两条加到真实的DOM里,而不用把整个DOM重新渲染。
那这是虚拟DOM以及他的作用
那虚拟DOM就是针对真实DOM做的一个一一映射的类似虚拟的数据结构,有了变化再把数据结构渲染到DOM里,做到局部的变化,实现行能优化。
那我们怎么实现它呢
- 以下代码为将数据结构如何转化为虚拟DOM,然后将虚拟DOM渲染到页面中变成真正的DOM。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| class VNode { constructor(tag, children, text) { this.tag = tag this.text = text this.children = children }
render() { if(this.tag === '#text') { return document.createTextNode(this.text) } let el = document.createElement(this.tag) this.children.forEach(vChild => { el.appendChild(vChild.render()) }) return el } }
function v(tag, children, text) { if(typeof children === 'string') { text = children children = [] } return new VNode(tag, children, text) }
/* //虚拟的JSON格式的数据结构 let nodesData = { tag: 'div', children: [ { tag: 'p', children: [ { tag: 'span', children: [ { tag: '#text', text: 'hi' } ] } ] }, { tag: 'span', children: [ { tag: '#text', text: 'oo' } ] } ] }
*/
let vNodes = v('div', [ v('p', [ v('span', [ v('#text', 'hi') ] ) ] ), v('span', [ v('#text', 'oo') ]) ] ) console.log(vNodes.render())
|
什么是diff
我们需要做一些改变的时候,比如增删改查,那么我们需要将改变后的虚拟DOM树与真实的DOM树做对比,找出差异,然后做到局部更新改变的地方,那么找出差异就是diff(算法找两棵DOM树的差异)。
以下代码为简单的实现 DOM diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 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(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 vNode1 = v('div', [ v('p', [ v('span', [ v('#text', 'hi') ] ) ] ), v('span', [ v('#text', 'oo') ]) ] )
let vNode2 = v('div', [ v('p', [ v('span', [ v('#text', 'hi') ] ) ] ), v('span', [ v('#text', 'oo'), v('#text', 'xx') ]) ] ) const root = document.querySelector('#root') patchElement(root, vNode1) patchElement(root, vNode1,vNode2)
|