vlambda博客
学习文章列表

虚拟dom原理:用js对象来描述页面

 作为计算机工程师,框架是实际开发中都会用到的。理解框架原理,对更好地使用它和定位问题是很有帮助的事情。本文实现了一个简单的vdom渲染过程,来帮助理解vdom原理。

vdom对象

vdom也叫虚拟dom,是用来描述页面的js对象。

在原生开发中,我们用HTML语言来描述web页面,它是由元素构成的。元素可拥有属性和文本内容。例如,下面的html代码:

<ul>
    <li class="item" style="color:red;" onclick="alert(0)">a</li>
    <li>b</li>
</ul>

它是一个ul元素,有两个li子元素。其中,li元素它有三个属性:class 、样式style和事件click,同时还有文本内容a、b。

这些元素信息,我们用一种数据结构来表示,如下:

{
    tag'ul',
    children: [{
        tag'li',
        props: {
            class:['item'],
            style: {
                color:"red"
            },
            on:{
                clickfunction({
                    alert(0);
                }
            }
        },
        children: [{
            text:'a'
        }]
    },
    {
        tag'li',
        children: [{
            text:'b'
        }]
    }]
}

vue.js和react框架就是用这样的vdom对象来描述页面的。根据vdom描述的元素信息,创建真实dom的过程,就是vdom的渲染。

vdom渲染

在浏览器内部,HTML页面是一个DOM树的结构。我们可以用Documentapi 创建新的元素,并挂载到DOM树。

首先,根据不同的节点类型,创建元素节点或文本节点,并设置元素节点的属性。

function render(vdom{
    if (vdom?.tag) {
        // 创建元素节点
        const dom = document.createElement(vdom.tag);
        // 设置属性
        for(let prop in vdom.props){
            setAttribute(dom, prop, vdom.props[prop]);
        }
        return dom;
    }else if(vdom?.text){
        // 创建文本节点
        return document.createTextNode(vdom.text);
    }
};

function setAttribute(dom, prop, value{
    switch(prop){
        case 'class':
            if(value instanceof Array){
                dom.setAttribute('class', [].concat(value).join(' '));
            }
            break;
        case 'style':
            if(typeof value == 'object'){
                Object.assign(dom.style, value);
            }
            break;
        case 'on':
            if(typeof value == 'object'){
                for(let event in value){
                    dom.addEventListener(event.toLowerCase(), value[event]);
                }
            }
            break;
        default:
            dom.setAttribute(prop, value);
    }
}

然后,处理节点的层级关系:如果元素节点有子节点则递归处理,并把创建好的节点挂载到父节点,构成完整的DOM树。

function render(vdom, parent){
    if (vdom?.tag) {
        const dom = document.createElement(vdom.tag);
        for (const prop in vdom.props) {
            setAttribute(dom, prop, vdom.props[prop]);
        }
        for (const child of vdom.children) {
            render(child,dom);
        }
        return mount(parent,dom);
    }else if(vdom?.text){
        return mount(parent,document.createTextNode(vdom));
    }
};
function mount(parent, elm){
    return parent ? parent.appendChild(elm) : elm;
}

到这里,我们就完成了一个vdom渲染的过程。

测试代码

<body>
    <div id="root"></div>
</body>
<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        render(ulVdom, document.getElementById("root"));
    }
</script>

image-20220319214806457

总结

vdom是一个用来描述页面的js对象。根据vdom描述的元素信息来创建真实dom的过程,就是vdom的渲染。首先,根据不同的节点类型,创建元素节点或文本节点,并设置元素节点的属性。然后,处理节点的层级关系:如果元素节点有子节点则递归处理,并把创建好的节点挂载到父节点,构成完整的DOM树。

参考资料

菜鸟教程-HTML

mdn-element