• React的虚拟DOM


    上一篇文章中,DOM树的信息可以用JavaScript对象来表示,反过来,可以根据这个用JavaScript对象表示的树结构来真正构建一颗DOM树。
    用JavaScript对象表示DOM信息和结构,当状态变更的时候,重新渲染这个JavaScript的对象结构,当然这样做,其实并没有更新到真正的页面上。

    但是可以用新渲染的对象树和旧的树进行对比,记录这两棵树的差异。记录下来的不同就是我们需要对页面真正的DOM操作,然后把它们应用在真正的DOM树上,页面才会真正变更。

    Virtual DOM算法的实现步骤

    1.用JavaScript对象结构表示DOM树的结构,然后用这个树构建一个真正的DOM树,插到文档中。

    //函数参数的定义,解构赋值中的一个用途
    function Element({tagName,props,children}){
        if(!(this instanceof Element)){
            return new Element({tagName,props,children});
        }
    
        this.tagName = tagName;
        this.props = props || {};
        this.children = children || [];
    }
    

    2.当状态变化的时候,重新构建一颗新的对象树,然后用新的树和旧的树进行比较,记录两颗树的差异。
    3.把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。
    Virtual DOM实质上是在JS和DOM之间做了一个缓存,可以类比cpu和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存,既然DOM这么慢,就在它们JS和DOM之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。

    步骤一:见前一篇博客

    步骤二:比较两颗虚拟DOM树的差异

    1.深度优先遍历,记录差异
    在实际的代码中,会对新旧两颗树进行一个深度优先的遍历,这样每个节点都会有一个唯一的标记。

    在深度优先遍历的时候,每当遍历到一个节点的时候就会和新的DOM树进行比较,如果有差异,就会记录到一个对象里面。
    2.这里会涉及到差异类型
    (1)替换掉原来的节点,例如把上面的div换成了section
    (2)移动、删除、新增子节点
    (3)修改节点的属性
    (4)对于文本节点,文本内容可能会改变。

    步骤三:把差异引用到真正的DOM树上

    因为步骤一所构建的JavaScript对象树和render出来的真正的DOM树的信息、结构是一样的。所以我们可以对那颗DOM树也进行深度优先遍历,遍历的时候从步骤二生成的差异对象中找出当前遍历的节点差异,然后进行DOM操作。

    虚拟DOM算法的思想

    标准的DOM机制是用户在应用上的操作实际反映的是直接对真实的dom进行操作,而在React中,用户在应用中对dom的操作实际上是对虚拟dom的操作,用户的操作产生的数据改变或者state变量改变都会应用到虚拟dom上,之后再批量的对这些更改进行diff算法计算,对比操作前后的虚拟dom树,把更改后的变化再同步到真实dom上。(在虚拟dom上执行多次修改,在真实dom中,只会执行一次dom操作,因为在React虚拟dom机制中,它会把所有的操作都合并,只会对比刚开始的状态和最后操作的状态,两者中找出不同,然后再同步到真实dom中。)

    react的独特的diff算法

    diff算法的处理方法,对操作前后的dom树的同一层的节点进行对比,一层一层对比。,这样时间复杂度就为o(n)。

    比较两颗虚拟DOM树的差异

    在用js对象表示DOM结构后,当页面状态发生变化时需要操作DOM的时候,我们可以先通过虚拟DOM计算出新的DOM树和旧的DOM树之间的最小修改量,然后最后一次性的将差异修改到真实DOM上。
    下图是一个简单的两个虚拟DOM之间的差异

    但是真实的场景下的DOM结构很复杂,我们必须借助于一个有效的DOM树比较算法。
    -(1)如何比较两个DOM树

    • (2)如何记录节点之间的差异
    如何比较两个DOM树

    计算两颗树之间的差异的常规算法复杂度是O(n的3次方),一个文档的DOM结构有上百个节点是正常的情况,这种复杂度不能应用于实际项目。所以,针对前端的具体情况,我们很少跨级别的修改DOM节点,通常是修改节点的属性、调整子节点的顺序、添加子节点等。所以我们一般对同级别节点进行比较,避免了diff算法的复杂性。对同级节点进行比较的常用方法就是深度优先遍历。在深度优先遍历过程中,每个节点都会有一个唯一的标记。在深度优先遍历的时候,每遍历到一个节点就把该节点和新的树进行对比。如果有差异就记录到一个对象里面。

    如何记录节点之间的差异

    由于我们对DOM采取的是同级比较,因此节点之间的差异可以归结为四种类型

    • 修改节点属性:用PROPS表示
    • 修改文本节点内容: 用TEXT表示
    • 替换原有节点,用REPLACE表示
    • 调整子节点,包括移动、删除、添加等,用RECODER表示
      对于节点之间的差异,可以很方便的使用上述四种方式进行记录,比如当旧节点被替换的时候:
    {type:REPLACE,node newNode}
    

    当旧节点的属性修改时

    {type:PROPS,props newProps}
    

    每个节点都有一个编号,如果对应的节点有变化,那么就把相应变化的类别记录下来即可。最后将这个差异对真实DOM做最小化的修改。

  • 相关阅读:
    第26月第13天 hibernate导包
    第26月第9天 getActionBar为空的解决办法
    第26月第8天 android studio 国内
    第26月第7天 mac如何matplotlib中文乱码问题
    第26月第6天 selenium
    第26月第3天 java gradle
    第26月第2天 vim javacomplete
    第25月第26天 dispatch_group_t dispatch_semaphore_t
    第25月25日 urlsession
    第25月第22日 django channels
  • 原文地址:https://www.cnblogs.com/sminocence/p/8284347.html
Copyright © 2020-2023  润新知