虚拟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