React虚拟DOM和DOM Diff算法

一:虚拟DOM

1. 为什么要用虚拟DOM

真实DOM的操作十分耗费性能,操作过于频繁会带来非常大的性能损耗。

2. 原理

用js的对象结构表示DOM树结构,然后利用这个js对象构建 一个真正的DOM树,插入到文档当中。相当于在JS和真实DOM中间加了一个缓存。编码时基本上只需要操作虚拟DOM,React会将虚拟DOM变化转为真实DOM变化,然后更新界面,大大减少了DOM操作。

3. 图解

虚拟DOM图解

4. 用jsx手动实现虚拟DOM

基本思路:

  1. 创建一个变量存储dom
  2. 手动实现一个render方法,将虚拟dom对象转化为真实dom
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <script type="text/babel">
        // 不引入react要解析jsx,必须进行以下操作
        // @jsx是babel的自执行指令,指定babel执行自定义的createElement
        /*@jsx createElement*/

        /*
         此方法会递归的将html转为对象的形式
         nodename:节点名字
         attr:节点属性
         args:后代节点
         */
        function createElement(nodeName, attr, ...args) {
            return { nodeName, attr, children: [].concat(...args) }
        }
        let users = [
            { name: "张三" },
            { name: "李四" },
            { name: "王五" }
        ];
        let vDom = (<div id="app" name="app">
            hello world!
            <ul>
                {
                    users.map((item, index) => {
                        return (
                            <li key={index}>{item.name}</li>
                        )
                    })
                }
            </ul>
        </div>);
        /* 自定义render方法 */
        function render(vnode) {
            if (vnode.split) { // 如果是文本,创建文本节点
                return document.createTextNode(vnode);
            }
            // 如果不是文本,创建根节点
            let node = document.createElement(vnode.nodeName);
            // 添加属性
            let attr = vnode.attr || {};
            // 返回attr自身可枚举属性,并组成一个数组,然后遍历
            Object.keys(attr).forEach((k) => {
                node.setAttribute(k, attr[k]);
            });
            // 添加子节点
            (vnode.children || []).forEach((n) => {
                node.appendChild(render(n))
            });
            return node;
        }
        let dom = render(vDom);
        document.body.appendChild(dom);
</script>

二:DOM Diff算法

1. 为什么要用DOM Diff

React利用虚拟DOM,可以避免频繁操作真实DOM。但是虚拟DOM最终都会被React转化为真实DOM,DOM操作耗费性能,如果每次更新状态都重新渲染整个应用或者整个组件,将会损耗非常大的性能。为了减少DOM更新,我们需要找到渲染前后真正变化的部分,然后更新这部分。而对比变化,找出需要更新部分的算法,我们称之为Diff算法。

2. 原理

  1. 初始化时render()函数创建了一颗虚拟DOM树,在下一次更新时,render()函数创建一颗新的虚拟DOM树,然后用Diff算法对两颗树进行对比。
  2. 传统的Diff算法的事件复杂度为o(n^3),React通过大胆的策略,将复杂度将为o(n)。
  • 两个不同类型的元素会产生不同的树
  • 对于同一层级的一组子节点,它们可以通过key值进行区分
  • 基于以上两个前提策略,React分别对tree diff、component diff、element diff三种diff方法进行算法优化
1. Tree Diff

概念:将新旧两颗虚拟DOM树,按照层级对应的关系,从头到尾遍历一遍。
在这里插入图片描述

  • 只会对相同颜色框(同级)的DOM节点进行比较,即同一父节点下的所有子节点。

在这里插入图片描述

  • 我们以为的操作:
    A.parent.remove(A);
    D.append(A);
  • React中的操作
    A.destroy();
    A= new A();
    A.append(new B());
    A.append(new C());
    D.append(A);
  • 当发现该节点已经不存在,则该节点及其子节点将会被完全删除掉,不会用于进一步比较(这也是算法的时间复杂度能够降低到o(n)的原因)
2. Component Diff

将同一位置上的组件进行对比,这种对比方法其实比较的就是类型

  • 如果类型相同,暂不更新
  • 如果类型不相同,就需要更新(删除旧的组件,再创建一个新的组件,插入到删除组件的那个位置)
3. Element Diff

在类型相同的组件内,再继续对比组件内部的元素,查看其是否相同。找到不同的元素,进行针对性修改,这就是Element Diff
三种节点操作:

  • 插入:新的Component类型不在旧集合内,执行插入操作
  • 移动:旧的集合包含新的Component,执行移动操作,复用之前的DOM节点(有key值才会进行移动,否则只是简单粗暴的插入和删除)
  • 删除:旧的Component不再新的集合里,或者旧的Component的类型相同但是Element不同,不能复用,执行删除操作。
4. 总结
  • DOM Diff通过Tree Diff,Component Diff,Element Diff逐层进行节点比较,极大的优化了传统的Diff算法。
  • 在实现自己的组件时,保持稳定的DOM结构,有助于性能的提升。例如:可以通过CSS隐藏或者显示某些节点,而不是真的移除或者添加DOM节点。
  • 在进行Component和Element对比的时候,为了提高对比效率,React推荐我们为每个for循环创建出来的元素或者组件,提供一个唯一的key。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值