《百分百问的面试题》Diff算法

158 阅读1分钟
其实没什么好讲、自己敲一遍就知道了。
本质就是对比新旧虚拟dom找出区别。
对比内容:节点、属性和子节点(子节点里有一样的三大元素:节点、属性和子节点)
对比结果:没有就增加、有且不相等就修改、旧vDOM有和新vDOM没有就删除和替换等。

复制粘贴到HTML上,即可实现功能

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Diff算法</title>
</head>
<body>
<div id="app"></div>
<script>
    /**
     * Element 类
     * @method new
     * @param type 类型
     * @param props 参数
     * @param children 子节点 = 数组 || 文字
     */
    class Element {
        constructor(type, props, children) {
            this.type = type;
            this.props = props;
            this.children = children;
        }
    }
    // 创建元素
    function createElement(type, props, children) {
        return new Element(type, props, children);
    }


    // 虚拟DOM 1
    const vDOM = createElement('ul',
        {
            class: 'list',
            style: 'width: 300px; height: 300px; background-color: orange'
        },
        [
            createElement('li',
                {class: 'item', 'data-index': 0},
                [
                    createElement('p', {class: 'text'}, ['第1个选项'])
                ]),

            createElement('li',
                {class: 'item', 'data-index': 1},
                [
                    createElement('p', {class: 'text'}, [
                        createElement('span', {
                            class: 'text'
                        }, [
                            '第2个列表选项'
                        ])
                    ])
                ]
            ),

            createElement('li',
                {class: 'item', 'data-index': 2},
                ['第3个列表选项']
            )
        ]
    );
    // 虚拟DOM2
    // 有需改内容
    let vDOM2 = createElement('ul',
        {
            class: 'list-wrap',
            style:'width: 300px; height: 300px; background-color: orange'
        },
        [
            createElement('li',
                {class: 'item', 'data-index':0 },
                [
                    createElement('p', {class:'title'},['特殊选项'])
                ]),

            createElement('li',
                {class: 'item', 'data-index':1 },
                [
                    createElement('p', {class:'text'},[
                        '第二选项'
                    ])
                ]
            ),

            createElement('li',
                {class: 'item', 'data-index':2 },
                ['第333个列表选项12']
            )
        ]
    );
    /**
     *  diff算法
     *  @method diff
     *  @param value 旧的虚拟DOM
     *  @param newValue 新的虚拟DOM
     *  @return patches 新旧的差别 => 补丁
     * */
    let patches = {}; // 补丁
    let vnIndex = 0; // 虚拟节点index

    function diff(value, newValue) {
        let index = 0;
        vNodeWalk(value, newValue, index);
        return patches;
    }
    /**
     *  节点遍历器
     *  @method vNodeWalk
     *  @param value 旧的虚拟DOM
     *  @param newValue 新的虚拟DOM
     *  @param index 下标
     *  @return patches 新旧的差别 => 补丁
     * */
    function vNodeWalk(oldNode, newNode, index) {
        // 虚拟节点补丁
        let vnPatch = [];

        // 新节点不存在
        if(!newNode){
            // 代表旧节点要做删除
            vnPatch.push({
                type: 'REMOVE',
                index,
            })
        } else if(typeof oldNode ==='string' && typeof newNode === 'string'){
            // 如果都是文本, 并且不相等代表增加文本
            if(oldNode !== newNode){
                vnPatch.push({
                    type: 'TEXT',
                    text: newNode
                })
            }
        } else if (oldNode.type === newNode.type) {
            // 同类型、需要判断是修改属性还是新增属性
            // 返回属性补丁
            const attrPatch = attrsWalk(oldNode.props, newNode.props)
            // 有补丁的情况下
            if (Object.keys(attrPatch).length > 0){
                vnPatch.push({
                    type: 'ATTR', // 修改
                    attrs: attrPatch
                })
            }
            // 遍历子节点
            childrenWalk(oldNode.children, newNode.children);
        } else {
            vnPatch.push({
                type: 'REPLACE', // 替换
                newNode
            })
        }

        // 判断是否有补丁
        if (vnPatch.length > 0){
            patches[index] = vnPatch;
        }


    }
    /**
     * 子节点遍历器
     * @method childrenWalk
     * @param oldChild 旧节点
     * @param newChild 新节点
     * 重新调用节点遍历器
     * */
    function childrenWalk(oldChild, newChild){
        oldChild.map((child, index) => {
            vNodeWalk(child, newChild[index], ++vnIndex);
        })
    }
    /**
     * 属性遍历器
     * @method attrsWalk
     * @param oldAttrs 旧属性
     * @param newAttrs 新属性
     * @return attrPatch 属性补丁
     * */
    function attrsWalk(oldAttrs, newAttrs) {
        let attrPatch = {};
        // oldAttrs的属性、 如果newAttrs也有。并且值不一致的情况下
        // 修改属性的值
        Object.keys(oldAttrs).forEach(key => {
            if(oldAttrs[key] !== newAttrs[key]){
                attrPatch[key] = newAttrs[key];
            }
        });

        // oldAttrs没有新属性就增加
        Object.keys(newAttrs).forEach(key => {
            if(!oldAttrs.hasOwnProperty(key)){
                attrPatch[key] = newAttrs[key]
            }
        });
        return  attrPatch
    }
    // diff算法后的补丁: result
    const result =  diff(vDOM,vDOM2);

    console.log(result);
</script>
</body>
</html>