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);
    }
    
    
  • 相关阅读:
    开启Tomcat APR运行模式,优化并发性能
    KVM与XEN虚拟化环境究竟有何不同
    fio测试freenas共享的iscsi磁盘性能
    ubuntu 16.04网速监控脚本
    centos 7网速监控脚本
    ubuntu 16.04 配置远程连接
    iometer测试磁盘IO性能
    pip 使用豆瓣源
    raid write back / write throught
    卡常数技巧
  • 原文地址:https://www.cnblogs.com/dshvv/p/15222147.html
Copyright © 2011-2022 走看看