vue dom diff算法

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>dom diff算法</title>
</head>
<body>
    <div id="root"></div>
    <script src="js/element.js"></script>
    <script src="js/diff.js"></script>
    <script src="js/patch.js"></script>
    <script>
        /*
        let virtualDom = createElement('ul', {class: 'list'}, [
                createElement('li', {class: 'item'}, ['a']),
                createElement('li', {class: 'item'}, ['b']),
                createElement('li', {class: 'item'}, ['c']),
            ]
        )
        renderDom(render(virtualDom), document.getElementById('root'))
        */
        let virtualDom1 = createElement('ul', {class: 'list'}, [
                createElement('li', {class: 'item'}, ['a1']),
                createElement('li', {class: 'item'}, ['b']),
                createElement('li', {class: 'item'}, ['c1']),
            ]
        )
        let virtualDom2 = createElement('ul', {class: 'list1'}, [
                createElement('li', {class: 'item'}, ['1']),
                createElement('li', {class: 'item'}, ['b']),
                createElement('div', {class: 'item'}, ['3']),
            ]
        )
        let el = render(virtualDom1);
        renderDom(el, document.getElementById('root'))
        let patchs = diff(virtualDom1, virtualDom2);
        // 将差异element放到页面中
        patchRender(el, patchs)
    </script>
</body>
</html>

element.js

/*
* @desc 创建标签
* @params
* {String} type 属性类型
* {Object} props 属性值
* {Array} children 子集
* */
class Element{
    constructor(type, props, children) {
        this.type = type;
        this.props = props;
        this.children = children;
    }
}

// eslint-disable-next-line no-unused-vars
/*
* @desc 创建标签
* @params
* {String} type 属性类型
* {Object} props 属性值
* {Array} children 子集
* */
function createElement(type, props, children) {
    return new Element(type, props, children)
}

/*
* @desc 设置属性
* @params
* {Element} node 当前标签
* {String} key 属性
* {String, ...} value 属性值
* */
function setAttr(node, key, value) {
    switch(key){
        case 'value':
            if(node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() === 'textarea'){
                node.value = value
            }
            else{
                node.setAttribute(key, value)
            }
            break;
        case 'style':
            node.style.cssText = value;
            break;
        default:
            node.setAttribute(key, value);
            break;
    }

}

// eslint-disable-next-line no-unused-vars
/*
* @desc 创建标签,并且设置属性
* @params
* {Object} eleobj 虚拟树
* */
function render(eleobj) {
    let el = document.createElement(eleobj.type);
    for(let key in eleobj.props){
        setAttr(el, key, eleobj.props[key])
    }
    eleobj.children.forEach(child => {
        child = (child instanceof Element) ? render(child) : document.createTextNode(child);
        el.appendChild(child);
    })
    return el;
}

// eslint-disable-next-line no-unused-vars
/*
* @desc 将渲染好的标签添加到页面中
* @params
* {Element} el 渲染好的树
* {Element} target 目标位置
* */
function renderDom(el, target) {
    target.appendChild(el)
}

diff.js

/*
* @desc diff算法
* @params
* {Object} oldTree 旧树
* {Object} newTree 新树
* @return
* {Object} 补丁包
* @example
* {1: [], 2: []}
* */
function diff(oldTree, newTree) {
    let patches = {};
    let index = 0;
    // 对比
    walk(oldTree, newTree, index, patches)
    return patches;
}

/*
* @desc 对比属性
* @params
* {Object} oldAttrs 旧属性
* {Object} newAttrs 新属性
* @return
* {Object} 返回带有差异的属性
* */
function diffAttr(oldAttrs, newAttrs) {
    let obj = {};
    for(let key in oldAttrs){
        let newAttr = newAttrs[key];
        let oldAttr = oldAttrs[key];
        if(newAttr !== oldAttr){
            obj[key] = newAttr; // 如果新属性上某一个属性被删除,则为undefined
        }
    }
    // 以下为旧属性没有,新属性有值
    for(let key in newAttrs){
        let newAttr = newAttrs[key];
        if(!oldAttrs.hasOwnProperty(key)){
            obj[key] = newAttr;
        }
    }
    return obj;
}

/*
* @desc 对比新旧树子节点
* @params
* {Array} oldChildren 旧子节点
* {Array} newChildren 新子节点
* {Object} patches 两棵树子元素的差异
* */
let Index = 0;
function diffChildren(oldChildren, newChildren, patches) {
    oldChildren.forEach((child, idx) => {
        walk(child, newChildren[idx], ++Index, patches)
    })
}

/*
* @desc 判断是否为为文本节点
* @params
* {Element} node 要判断的元素
* */
function isString(node) {
    return Object.prototype.toString.call(node) === '[object String]'
}

/*
* @desc 将两棵树差异返出来
* @params
* {Object} oldTree 旧树
* {Object} newTree 新树
* {Number} index 当前children的下标
* {Object} patches 两棵树的差异
* */
function walk(oldTree, newTree, index, patches) {
    let currentPath = [];
    // 节点被删除
    if(!newTree){
        currentPath.push({type: 'REMOVE', index})
    }
    // 判断是否是文本节点
    else if(isString (oldTree) && isString(newTree)){
        if(oldTree !== newTree){
            currentPath.push({type: 'TEXT', text: newTree})
        }
    }
    // 节点标签完全一样
    else if(oldTree.type === newTree.type){
        // 比较属性是否有更改
        let attrs = diffAttr(oldTree.props, newTree.props);
        if(Object.keys(attrs).length > 0){
            currentPath.push({type: 'ATTRS', attrs})
        }
        // 如果有子节点,遍历子节点
        diffChildren(oldTree.children, newTree.children, patches)
    }
    // 节点被替换
    else{
        currentPath.push({type: 'REPLACE', newTree})
    }
    // 当前元素有差异
    if(currentPath.length > 0){
        patches[index] = currentPath
    }
}

patch.js

/*
* @desc 将两棵树差异性放到页面中
* @params
* {Element} el 将旧virtualDom转化为element
* {Object} patches 新旧两棵树差异
* @example
* patches:   {0: [attrs: {class: "list1"},type: "ATTRS"], 2: Array(1), 6: [text: "3",type: "TEXT"]}
* */

let allPatches;
let index = 0; // 默认哪个属性需要打补丁
function patchRender(el, patches) {
    console.log(patches)
    allPatches = patches;
    walkRender(el)
}

/*
* @desc 渲染子节点
* @params
* {Element} node 旧有的dom
* */

function walkRender(node) {
    // 将patches里面的0,1,2...当做key值,取出对应数组
    let currentPath = allPatches[index++];
    let childNodes = node.childNodes;
    childNodes.forEach(child => {
        walkRender(child)
    })
    if(currentPath){
        doPath(node, currentPath)
    }
}

/*
* @desc 设置属性
* @params
* {Element} node 当前标签
* {String} key 属性
* {String, ...} value 属性值
* */
function setAttr(node, key, value) {
    switch(key){
        case 'value':
            if(node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() === 'textarea'){
                node.value = value
            }
            else{
                node.setAttribute(key, value)
            }
            break;
        case 'style':
            node.style.cssText = value;
            break;
        default:
            node.setAttribute(key, value);
            break;
    }

}

/*
* @desc 将不定渲染到页面中
* @params
* {Element} node 旧有的dom
* {Array} patches 每一级对应不定数组
* */
function doPath(node, patches) {
    console.log(patches)
    patches.forEach(patch => {
        switch (patch.type){
            case 'ATTRS':
                for(let key in patch.attrs){
                    let value = patch.attrs[key];
                    if(value){
                        setAttr(node, key, value)
                    }
                    else{
                        node.removeAttribute(key)
                    }
                }
                break;
            case 'TEXT':
                node.textContent = patch.text;
                break;
            case 'REPLACE':
                let newNode = (patch.newTree instanceof Element) ? render(patch.newTree) : document.createTextNode(patch.newTree);
                node.parentNode.replaceChild(newNode, node)
                break;
            case 'REMOVE':
                node.parentNode.removeChild(node);
                break;
            default:
                break;
        }
    })
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值