zoukankan      html  css  js  c++  java
  • 实现一个双向绑定和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);
    }
    
    
  • 相关阅读:
    Data Structure and Algorithm
    Data Structure and Algorithm
    Data Structure and Algorithm
    Data Structure and Algorithm
    Data Structure and Algorithm
    Data Structure and Algorithm
    Data Structure and Algorithm
    Data Structure and Algorithm
    Data Structure and Algorithm
    Data Structure and Algorithm
  • 原文地址:https://www.cnblogs.com/dshvv/p/15222147.html
Copyright © 2011-2022 走看看