虚拟DOM

作者: 泡杯感冒灵 | 来源:发表于2022-03-14 15:38 被阅读0次
React引入了虚拟DOM的概念,以提升性能!
React里,当state或者props改变的时候,render函数会重新执行,重新渲染页面。假设没有React,我们该怎么实现这个过程?
  1. state数据
  2. JSX模板
  3. 数据+模板 结合,生成真实的DOM,挂载到页面上显示。
  4. state发生改变。
  5. 数据+模板 结合,生成新的真实的DOM,替换原始的DOM,挂载到页面上展示。

这种方式的缺陷:(生成和替换DOM,会非常耗费性能)
步骤3,第一次生成了一个完整的DOM片段。
步骤5,第二次生成了一个完整的DOM片段。
步骤5,第二次的DOM,替换第一次的DOM。

对上边的方法进行改良
  1. state数据
  2. JSX模板
  3. 数据+模板 结合,生成真实的DOM,挂载到页面上显示。
  4. state发生改变。
  5. 数据+模板 结合,生成真实的DOM,并不直接替换原始的DOM
  6. 新的DOM(这里新的DOM ,就是DocumentFrament 是JS底层的文档碎片,它是在内存里,并没有真实挂载倒页面) 和 原始的DOM做比对,找出差异。
  7. 找到哪些是修改的,有差异的DOM。
  8. 只用新的DOM中的变化的元素,替换掉老的DOM中的元素。

这种方法的缺陷,性能提升并不明细
相比上边直接生成真实DOM并全部替换原始DOM,节约了性能。但是又损耗了一部分性能,那就是新的DOM和原始DOM做比对消化的性能。

虚拟DOM方案
  1. state数据
  2. JSX模板
  3. 数据+模板 结合,生产虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)
// 真实DOM节点
<ul id='list'>
  <li class='item'>hello</li>
</ul>

// 虚拟DOM对象
var element = {
  tagName: 'ul', // 节点标签名
  props: { // DOM的属性,用一个对象存储键值对
    id: 'list'
  },
  children: [ // 该节点的子节点
    {tagName: 'li', props: {class: 'item'}, children: ["hello"]},
  ]
}

  1. 用虚拟DOM的结构,生成真实的DOM,挂载到页面上显示。
  2. state发生变化
  3. 数据+模板结合,生成新的虚拟DOM(只生成新的虚拟DOM, 极大提升了性能)
// 新的虚拟DOM
var element = {
  tagName: 'ul', // 节点标签名
  props: { // DOM的属性,用一个对象存储键值对
    id: 'list'
  },
  children: [ // 该节点的子节点
    {tagName: 'li', props: {class: 'item'}, children: ["world"]},
  ]
}
  1. 比较原始虚拟DOM和新的虚拟DOM的区别,找到具体区别,直接操作DOM改变不一样的节点。(对比JS对象,极大提升了性能)

注意,只所以说极大的提升了性能:(用JS创建一个JS对象,并对比JS对象的差异,非常简单,性能代价很小,但是如果要创建一个真实DOM,需要调用Web Application级别的API,这个级别的API,性能损耗非常大)

注意JSX语法,并不是真实的DOM,而是一个模板, 没有JSX语法我们完全可以用React.createElement()来实现同样的功能,只不过比较麻烦。之所以用JSX语法,是因为这种语法,写起来更简洁。
// JSX语法 -> createElement -> 虚拟DOM(JS对象) -> 真实DOM
render(){
        return (
            <div>item</div>
        )
    }

render(){
        return React.createElement('div',{},'item')
    }

虚拟DOM,带来了什么好处?

  1. 性能提升了。
  2. 它使得跨端应用得以实现。React Native (用react语法去写原生应用)。为什么这么说呢,假如没有虚拟DOM,数据发生改变,拿到JSX模板,然后去渲染DOM,这在浏览器上是没有问题的,但是,在移动端的原生应用里,比如安卓或IOS机器上的代码里,是没有DOM的概念的,如果没有虚拟DOM,生成的DOM在原生应用里根本就无法使用。这个时候,我们的代码,只能运行在浏览器里。但是有了虚拟DOM ,因为它是一个JS对象,这个对象,在浏览器里和在原生应用里,都可以被识别。只不过,在浏览器里,虚拟dom用来生成真实DOM,在原生应用里,用来生成原生组件,所以,state和JSX模板,都可以被复用。所以,React即可以开发网页应用,又可以开发原生应用。
虚拟DOM中的Diff算法
  • 新老虚拟DOM差异的比对,需要用到diff算法,diff全称是diffrence(差异)。这种算法大大提升了两个虚拟DOM的比对性能。
  • 在React中,虚拟DOM,什么时候会被比对?当数据发生改变的时候,虚拟DOM才会被比对,数据的变化,要么是state,要么是props发生了改变,而props的改变,还是因为父组件的state发生了改变,所以归根节点,什么时候数据才会发生变化呢? 也就是调用setState方法的时候,数据才会发生变化,然后虚拟DOM才会发生比对。
  • setState方法是异步的,之所以设计为异步的,本质是为了提升react底层的性能,如果我们连续调用了3次setState方法,变更3组数据,此时页面会怎么做?会做3次虚拟DOM的比对,更新3次页面,那么如果这3次调用间隔时间都非常的小呢?做3次虚拟DOM的比对,更新3次页面会非常的浪费性能,此时react遇到连续调用3次setState方法的情况会怎么做呢? react会把3次setState,合并成1个setState方法,然后做一次虚拟DOM的比对,去更新一次DOM,这样就省去了额外的两次DOM比对造成的性能上的耗费。
  • diff算法采用同层比对方法。看下图,左侧是一开始生产的虚拟DOM,右侧是当数据发生变化的时候,生成的新的虚拟DOM,两个虚拟DOM做比对,找到差异后,去更新真实DOM。如果最顶层的DOM一致,再去比对下一层,以此类推,假设,虚拟DOM比对的时候,第一层DOM节点 就不一致,react就不会再继续往下比了,react会把原始页面的虚拟DOM这个节点下面的所有DOM,全部删除,重新生成一遍这个节点下的所有的DOM,用重新生成的DOM,替换原始页面上的DOM。哪怕第二层,第三层的DOM没有发生变化,也会删除重新生成,没法复用,这虽然会造成DOM节点渲染的浪费,但是这种同层比对的好处就是比对算法非常简单,只要一层一层往下的比对就行了,算法简单了,比对速度就非常快,大大减少了两个虚拟DOM比对的性能上的消耗
    image.png
  • 虚拟DOM的key值。如果没有key值,每个虚拟DOM节点就没有自己的名字,当我们做两个虚拟DOM树的比对的时候,节点和节点之间的关系,就会很难被确立。比对的时候,就需要做多层循环的比较,比较耗费性能。假如,我们在做虚拟DOM节点的循环的时候,可以给每个节点起个名字(也就是key),当数据发生变化的时候,虚拟DOM节点的比对会根据key值做关联,key一致的就可以复用了,只需要把key不一致的,增加到DOM树上就可以了,极大的提升了虚拟DOM比对的性能,前提是老的虚拟DOM节点上的key到了新的虚拟DOM上,还是保持原来的值,没有发生变化,这就是为什么我们在循环的时候,尽量不要用index做key的原因。key是index的话,是不稳定的,没有办法保证老的虚拟DOM的key跟新的虚拟DOM的key一致。所以,我们要用一个稳定的值,作为key,尽量不用index
    image.png

相关文章

网友评论

      本文标题:虚拟DOM

      本文链接:https://www.haomeiwen.com/subject/iowjdrtx.html