使用js对象简单模拟虚拟dom的渲染

vnode0数据:

var vnode0 = {
    tag: 'ul',
    props: {
        id :'ul1',
        style: {color: 'red', fontSize: '14px'},
        'data-index': 0,
    },
    on: {
        click:()=>console.log('click ul')
    },
    children: [
        {
            tag: 'li',
            children: 'a'
        },
        {
            tag: 'li',
            props: {
                className: 'list-item'
            },
            on: {
                click(e) {
                    // e.stopPropagation();
                    console.log('click li')
                }
            },
            children: [
                {
                    tag: 'a',
                    children: '好好学习'
                }
            ]
        },
    ]
}

一. 用js模拟dom结构:

1. 核心方法: document.createElement + document.createDocumentFragment(推荐):

let timer2 = new Date().getTime()
const vDom = document.createDocumentFragment() 
function render2(vnode) {
    const dom = document.createElement(vnode.tag)
    const props = _.get(vnode, 'props')
    if(!_.isEmpty(props)) {
        for(let key in props) {
            const item = _.get(props, [key]);
            if(key === 'className') {
                dom.class = item
            }
            else {
                dom.setAttribute(key, item)
            }
        }
    }
   
    if(_.get(vnode, 'props.style')) {
        const styleObj = vnode.props.style
        let styleStr = ''
        for(let key in styleObj) {
            const item = styleObj[key]
            styleStr+= `${key.replace(/[A-Z]/g, str=>'-'+str.toLowerCase())}:${item};`
        }
        dom.style = styleStr
    }
    if(_.get(vnode, 'on')) {
        for(let key in vnode.on) {
            const item = vnode.on[key]
            dom[`on${key}`]=item
        }
    }
    const children = _.get(vnode, 'children');
    
    if(typeof children === 'string') {
        dom.innerText = children
    }
   
    if(Array.isArray(children) && !_.isEmpty(children)) {
        for(let item of children) {
            const dom0 = render2(item);
            dom.appendChild(dom0)
        }
    }
    vDom.appendChild(dom)
    console.log('render2时间', new Date().getTime()-timer2)
    return dom
}

render2(vnode0)
document.getElementById('box').appendChild(vDom)

2. 使用innerHTML

注意事项:

1) 不适合需要在html字符串上加onclick等事件的情况, 要加等到页面渲染完成再需要找到那些dom元素自行添加

2) 适合那些直接把click事件加到父元素上的(比如box.addEventListener('click', e=>{...}), 通过冒泡获取子元素的属性key和属性值, 进行判断处理业务

3) 数据量大的时候比较消耗性能

let timer1 = new Date().getTime()
let str = '';
function render(vnode) {
    str += `<${vnode.tag}`;
    if (_.get(vnode, 'props.id')) {
        str += ` id="${vnode.props.id}"`
    }
    if (_.get(vnode, 'props.className')) {
        str += ` class="${vnode.props.className}"`
    }
    if (_.get(vnode, 'props.style')) {
        const styleObj = vnode.props.style
        let styleStr = ''
        for (let key in styleObj) {
            const item = styleObj[key]
            styleStr += `${key.replace(/[A-Z]/g, str => '-' + str.toLowerCase())}:${item};`
        }
        str += ` style="${styleStr}"`
    }
    if (_.get(vnode, 'on')) {
        for (let key in vnode.on) {
            const item = vnode.on[key]
            // onclick事件不能添加...
        }
    }
    const children = vnode.children;

    if (typeof children === 'string') {
        str += `>${children}`
    }

    if (Array.isArray(children) && !_.isEmpty(children)) {
        str += '>'
        children.forEach(element => {
            render(element)
        });
    }
    str += `</${vnode.tag}>`;
    console.log('render时间', new Date().getTime() - timer1)
    return str
}

render(vnode0);
document.getElementById('box').innerHTML = str;