实现一个数据驱动的Web前端框架(for指令尚未完成)

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View</title>
</head>
<body>

    <div id="app" y-if="is">
        <h1 :style="color">Hello MVVM</h1>
        <p>请欣赏此句:{{title}}</p>
        <div :innerhtml="html"></div>
        <img :src="img" alt="" style="width: 60px;height: 60px;">
        <br>
        <button onclick="changeImg()">改变图片</button>
        <br><br>
        <button onclick="changeHTML()">改变html</button>
        <br><br>
        <button onclick="changeColor()">改变Hello MVVM的字体颜色</button>
        <br><br>
        <button onclick="changeText()">改变'橘皮安知橘瓣之酸甜'文字</button>
        <br><br>
        <button onclick="removeApp()">移除所有元素 三秒之后重新加入dom</button>
    </div>

    <script src="view.js"></script>
    <script>
        let data = {  // 这里存放所有的响应式数据
            is: true, // 控制id为app的div的隐藏显示
            img: '1.jpg', // 控制图片
            html: '<h6>h6标签</h6>', // 控制html
            title: '橘皮安知橘瓣之酸甜', // 控制p标签显示的内容
            color: 'color: red;' // 控制h1标签的style属性
        };
        /**
         * 调用框架提供的MVVM方法 传入响应式数据 和 框架要接管控制的区域元素
         * 该方法返回一个函数 在你对响应式数据进行修改之后 要调用此函数通知框架进行数据更改
         * 原理和angular一样都用的脏值检查 只是我没有使用zone.js对所有异步进行包裹 所以需要手动调用change方法
         * angular如果关闭zonejs 也需要手动调用tick方法
         */
        let change = MVVM(data, document.getElementById('app'));
        function changeColor() {
            data.color = 'color: green;';
            change();
        }
        function changeHTML() {
            data.html = '<h1>h1标签</h1>';
            change();
        }
        function changeText() {
            data.title = '橘瓣难解橘皮之苦涩';
            change();
        }
        function removeApp() {
            data.is = false;
            change();
            setTimeout(() => {
                data.is = true;
                change();
            }, 3000);
        }
        function changeImg() {
            data.img = '2.jpg';
            change();
        }
    </script>
</body>
</html>

JS

/**
 * 这是一个js的模版语法库,力图使用模版语法进行web开发
 * 支持的语法如下:
 * 数据绑定:属性绑定 前面加冒号:  (已完成,特别对class和innerhtml做了增强,使用时直接:class :innerhtml即可)
 * 数据绑定:双花括号语法 {{}}  (已完成)
 * if指令:y-if 判断值为true时显示元素,为false移除元素  (已完成)
 * for指令:y-for 迭代生成元素 item in list  (未完成)
 */
function MVVM(bindData, e) {

    ;(function(data) {
        let keys = Object.keys(data);
        for (let i = 0, len = keys.length; i < len; ++i) {
            data['_y_' + keys[i]] = !data[keys[i]];
        }
    })(bindData);

    function getElementList(e) {
        let result = [];
        let eList = [e];
        while (eList.length) {
            let _eList = [];
            for (let i = 0, len = eList.length; i < len; ++i) {
                result.push(eList[i]);
                let temp = eList[i].childNodes;
                for (let j = 0, lens = temp.length; j < lens; ++j) {
                    let item = temp[j];
                    let type = item.nodeType;
                    if (type == 1) {  // 元素节点
                        _eList.push(item);
                    } else if (type == 3) {  // 文本节点
                        if (item.nodeValue.trim() != '') {  // 如果不是空白节点
                            result.push(item);
                        }
                    } else if (type == 8) {  // 注释节点
                        // 暂时不做处理
                    } else {
                        // 其他节点不做处理
                    }
                }
            }
            eList = _eList;
        }
        return result;
    }

    function parseText (e) {
        let result = [];
        let value = e.nodeValue;
        let mustache = new RegExp(/{{.*?}}/g);
        let arr1 = value.match(mustache);
        if (arr1 == null) {
            return null;
        }
        for (let i = 0, len = arr1.length; i < len; ++i) {
            arr1[i] = arr1[i].substr(2, arr1[i].length - 4);
        }
        value = value.replace(mustache, '$');
        let arr2 = value.split('$');
        if (arr2[0] == '') {
            arr2.length = arr2.length - 1;
            for (let i = 0, len = arr1.length; i < len; ++i) {
                result.push({ isStr: false, value: arr1[i] });
                result.push({ isStr: true, value: arr2[i] });
            }
        } else {
            if (arr2[arr2.length - 1].trim() == '') {
                arr2.length = arr2.length - 1;
            }
            [arr1, arr2] = [arr2, arr1];
            for (let i = 0, len = arr1.length; i < len; ++i) {
                result.push({ isStr: true, value: arr1[i] });
                result.push({ isStr: false, value: arr2[i] });
            }
        }
        return {
            type: 'text', 
            e: e, 
            value: result, 
            old: ''
        };
    }

    function parseElement (e) {
        let attributes = e.attributes;
        let result = [];
        for (let i = 0, len = attributes.length; i < len; ++i) {
            let temp = attributes[i];
            if (temp.nodeName[0] == ':') {  // 属性绑定
                let attrName = temp.nodeName.substr(1);
                attrName == 'class' && (attrName = 'className');
                attrName == 'innerhtml' && (attrName = 'innerHTML');
                result.push({
                    type: 'attr', 
                    e: e, 
                    attrName: attrName, 
                    value: temp.nodeValue
                });
                continue;
            }
            if (temp.nodeName == 'y-if') {  // if指令
                let div = document.createElement('div');
                e.insertAdjacentElement('beforebegin', div);
                e.parentNode.removeChild(e);
                result.push({
                    type: 'y-if', 
                    e: e, 
                    location: div, 
                    value: temp.nodeValue
                });
                continue;
            }
            if (temp.nodeName == 'y-for') {  // for指令
                continue;
            }
        }
        return result;
    }

    function parseTemplate(list) {
        let result = [];
        for (let i = 0, len = list.length; i < len; ++i) {
            if (list[i].nodeType == 1) {  // 若为元素节点
                let temp = parseElement(list[i]);
                for (let i = 0, len = temp.length; i < len; ++i) {
                    result.push(temp[i]);
                }
            } else {  // 若为文本节点
                let temp = parseText(list[i]);
                if (temp != null) {
                    result.push(temp);
                }
            }
        }
        return result;
    }

    let mapping = parseTemplate(getElementList(e));  // 保存所有的模版映射关系

    function change() {  // 脏检查
        for (let i = 0, len = mapping.length; i < len; ++i) {
            let tempE = mapping[i];
            if (tempE.type == 'text') {  // 如果是文本节点(处理mustache语法)
                let value = '';
                let temp = mapping[i].value;
                for (let j = 0, lens = temp.length; j < lens; ++j) {
                    if (temp[j].isStr) {
                        value += temp[j].value;
                    } else {
                        value += bindData[temp[j].value];
                    }
                }
                if (mapping[i].old != value) {
                    mapping[i].old = mapping[i].e.nodeValue = value;
                }
            } else if (tempE.type == 'attr') {  // 如果是元素节点(处理属性绑定)
                let _new = bindData[tempE.value];
                if (bindData['_y_' + tempE.value] != _new) {
                    bindData['_y_' + tempE.value] = tempE.e[tempE.attrName] = _new;
                }
            } else if (tempE.type == 'y-if') {  // 如果是元素节点(处理y-if指令)
                let _new = bindData[tempE.value];
                if (bindData['_y_' + tempE.value] != _new) {
                    bindData['_y_' + tempE.value] = _new;
                    if (_new) {
                        tempE.location.insertAdjacentElement('afterend', tempE.e);
                    } else {
                        tempE.e.parentNode.removeChild(tempE.e);
                    }
                }
            } else if (tempE.type == 'y-for') {  // 如果是元素节点(处理y-for指令)

            } else {}
        }
    }

    change();

    return change;
}

 

posted @ 2020-11-19 15:56  吕洋  阅读(124)  评论(0)    收藏  举报