去掉虚拟两个字,先了解下什么是DOM?

在原生JS中有DOM操作,就是使用浏览器提供的一些API,选中一个元素对它进行操作,比如说:增加一个属性、增加一个孩子、修改它里面的内容、改变它的位置,直接对DOM操作。这个DOM实际上对应的就是浏览器里面你所能看的见的对应的某个元素。

那什么是虚拟DOM

假设我们要操作一个数据的报表,这个报表大概有几百条数据,我们要对这个报表进行一个排序的操作

我们有这样一个表格,这里面大概有100多项
姓名 | 年纪 | 分数 | 等等
——|——|——|——
小红 | 12 | 44 |
小花 | 13 | 78 |

  1. 当我去点年纪的时候,希望年纪里的所有DOM元素可以按照年纪去排序,当我点新增的时候会在表格下面再新增一行数据,当我点击姓名的时候所有DOM按照姓名去做一个排序,我们可以想下用原生JS怎么去做排序,用JS去对DOM结构去排序,操作起来很难实现,也很麻烦。
  1. 后来我们有了MVVM框架可以对数据排序,数据对应到页面上的DOM结构,我们只需要对数据排序,那里面的DOM结构自然就排序了。
  1. 我们自己实现一个框架,假设数据变了,那我们的DOM结构也就变了,难道我们需要把数据重新渲染一次,如果用户频繁大量改动数据,DOM也会频繁改动,就会造成卡顿,那我们可以去做一些优化,怎么去做优化呢?
  1. 那我们可以像计算机内存那样,我们可以自己设定一个虚拟的数据结构,它是对真实的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)