实现一个双向绑定和v-model

发布订阅模式

我把发布订阅的实现类单独提出来,这样代码看起来简洁

/*
* 发布订阅
**/
class Pubsub {
    static instance = null;

    // 单例
    static getInstance() {
        if (Pubsub.instance == null) {
            Pubsub.instance = new Pubsub;
        }
        return Pubsub.instance;
    }

    // 注册的事件和处理器关联集合
    eventAndHandel = {};

    // 触发  
    emit(eventName, params) {
        if (this.eventAndHandel.hasOwnProperty(eventName)) {
            this.eventAndHandel[eventName].forEach(hander => {
                hander(params)
            });
        }
    }

    // 注册or订阅
    on(eventName, cbc) {
        if (!this.eventAndHandel.hasOwnProperty(eventName)) {
            this.eventAndHandel[eventName] = [cbc]
        } else {
            this.eventAndHandel[eventName].push(cbc)
        }
    }
}

第一版本 es5

最普通的语法

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>
        <h1 d-bind="name"></h1>
        <span d-bind="name"></span>:<span d-bind="age"></span>
    </div>
    <div>
        姓名:<input d-model="name" type="text"></br>
        性别:<input d-model="age" type="text"></br>
    </div>
    <!-- <script src="./lodash.js"></script> -->
    <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
    <script src="./helper.js"></script>
    <script>
        // ---------------------一个vue实例开始----------------------------


        const data = {
            name: '丁少华',
            age: 20
        };


        // v-model的实现
        const models = document.querySelectorAll(`[d-model]`);
        models.forEach(item => {
            const v = item.getAttribute('d-model');
            item.oninput = function ({ target: { value } }) {
                data[v] = value;
            }

        })

        // 双向绑定的实现
        const pubsub = Pubsub.getInstance();
        const data_ = _.cloneDeep(data);

        for (const key in data) {
            Object.defineProperty(data, key, {
                set(newValue) {
                    data_[key] = newValue;
                    pubsub.emit('vm', {
                        id: key,
                        value: newValue
                    })
                },
                get() {
                    return data_[key];
                }
            })
        }
        pubsub.on('vm', ({ id, value }) => {
            // 给普通节点复制
            const binds = document.querySelectorAll(`[d-bind=${id}]`);
            binds.forEach(item => {
                item.innerText = value;
            })

            // 给表单控件赋值
            const models = document.querySelectorAll(`[d-model=${id}]`);
            models.forEach(item => {
                item.value = value;
            })
        });

        // 初始化赋值


        for (const key in data) {
            data[key] = data[key];
        }

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

第二版 es6

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>
        <h1 d-bind="name"></h1>
        <span d-bind="name"></span>:<span d-bind="age"></span>
    </div>
    <div>
        姓名:<input d-model="name" type="text"></br>
        性别:<input d-model="age" type="text"></br>
    </div>
    <script>
        // ---------------------一个vue实例开始----------------------------
        const data = {
            name: '丁少华',
            age: 20
        };

        // ------------双向绑定的实现---------------
        const pubsub = Pubsub.getInstance();
        const data_ = new Proxy(data,{
            set(target, property, value, receiver){
                Reflect.set(...arguments);
                pubsub.emit('vm', {
                    id: property,
                    value: value
                })
            },
            get(){
                return Reflect.get;
            }
        })
        pubsub.on('vm', ({ id, value }) => {
            // 给普通节点复制
            const binds = document.querySelectorAll(`[d-bind=${id}]`);
            binds.forEach(item => {
                item.innerText = value;
            })

            // 给表单控件赋值
            const models = document.querySelectorAll(`[d-model=${id}]`);
            models.forEach(item => {
                item.value = value;
            })
            console.log(data_);
        });

        // 初始化赋值
        for (const key in data) {
            data_[key] = data[key];
        }


        // ---------v-model的实现-----------------
        const models = document.querySelectorAll(`[d-model]`);
        models.forEach(item => {
            const v = item.getAttribute('d-model');
            item.oninput = function ({ target: { value } }) {
                data_[v] = value;
            }

        })


    </script>
</body>

</html>

可以看到proxy很方便,我也不用使用lodash的深拷贝来进行隔离元数据了

vue编译模板

const compileHandel = (el, data) => {
    const childNodes = el.childNodes;
    const reg = /{{(.*)}}/;    // 表达式文本
    childNodes.forEach(node => {
        const text = node.textContent;
        const isElementNode = node.nodeType == 1; // 按元素节点方式编译
        if (isElementNode) {
            const nodeAttrs = node.attributes;
            for (const iterator of nodeAttrs) { //argument都实现了iterator接口,所以可以for of
                const { name: attrName, value: exp } = iterator;
                if (attrName.indexOf('v-') == 0) {
                    const dir = attrName.substring(2);
                    if (dir.indexOf('on') === 0) { // 事件指令
                        compileHandelHelper.eventDirective(data,node, exp, dir);
                    } else { // 普通指令
                        if(dir === 'model'){
                            compileHandelHelper.modelDirective(data,node, exp);
                        }
                    }
                }
            }
        } else {
            reg.test(text) && compileHandelHelper.mastache(data, node, RegExp.$1.trim());
        }
        // 遍历编译子节点
        if (node.childNodes && node.childNodes.length) {
            compileHandel(node, data);
        }
    })
}


const compileHandelHelper = {
    mastache(data, node, txt) { // 解析双大括号
        node.textContent = '123';
    },
    eventDirective() { // 解析事件指令

    },
    modelDirective(data, node, exp) { // 解析v-model指令
        node.oninput = function ({ target: { value } }) {
            
        }
    }
}


export default (el, data) => {
    compileHandel(el, data);
}

原文地址:https://www.cnblogs.com/dshvv/p/15222147.html