《Vue源码》实现virtualDOM转真实DOM

246 阅读1分钟
虚拟 DOM 只是一个 JS 的 Object 对象而已。它是虚拟的,需要用JS转成真实Dom对象。

虚拟DOM有什么优点

  1. 减少DOM操作

    1.虚拟DOM可以将多次操作合并为一次操作。

    2.虚拟DOM借助DOM diff 可以将多余的操作省略

  2. 跨平台

    虚拟DOM不仅可以变成DOM,还可以变成小程序、iOS应用,因为虚拟DOM本质是JS对象

复制粘贴到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>virtualDom</title>
</head>
<body>
<div id="app"></div>
<script>
    /**
     * 创建元素
     * @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
    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个列表选项']
            )
        ]
    );
    /**
     * 渲染函数虚拟DOM成真实的节点
     * @method render
     * @param vDOM 虚拟dom对象
     * @return 返回真实的节点
     */
    const render = (vDOM) => {

        // 解析赋值 取出类型、参数和子节点
        const {type, props, children} = vDOM;

        // 获得根节点、并且把之后虚拟type转成节点
        const el = document.createElement(type);

        // 遍历属性
        Object.keys(props).forEach(key => {
            // 设置节点的属性和值
            setAttrs(el, key, props[key]);
        });

        children.map(child => {
            // 如果通过Element生成接着渲染?
            // 是: 虚拟节点转元素
            // 否: 创建文本节点
            child = child instanceof Element
                ? render(child)
                : document.createTextNode(child);

            // 全部添加到每一级父元素、直到最顶级节点
            el.appendChild(child);
        });

        return el;
    };

    /***
     * 节点挂上属性
     * @method setAttrs
     * @param element 节点
     * @param property 属性
     * @param value 属性后的值
     */
    function setAttrs(element, property, value) {
        switch (property) {
            // 或许是输入框
            case 'value':
                // 目前没测试value属性
                if (element.tagName === 'INPUT' || element.tagName === 'TEXTART') {
                    // 输入框的值是value不是innerText
                    element.value = value;
                } else {
                    element.setAttribute(property, value);
                }
                break;
            // 如果是样式
            case 'style':
                element.style.cssText = value;
                break;
            // 默认情况就设置属性和值就好(不够严谨
            default:
                element.setAttribute(property, value);
                break;
        }
    }

    const realElement = render(vDOM);

    // 获取到App元素
    const App = document.querySelector('#app');

    // 蹬蹬蹬~虚拟DOM转真实DOM成功
    App.appendChild(realElement);

</script>
</body>
</html>