数据双向绑定 JS 类似 VEU 简单实现

对于 数据双向绑定 的核心其实就是 Object.defineProperty

Object.definedProperty方法可以在一个对象上直接定义一个新的属性、或修改一个对象已经存在的属性,最终返回这个对象。

当这个属性经过 definedProperty 设置 get、set后,对 属性的赋值和取值,都会调用 set 和 get,可以将其理解为 definedProperty 可以对 属性的 操作进行监控

<div id="app">
    <p v-bind:title="message" id="b">{{ message }}</p>
    <input v-model="message" id="a">
    <input v-model="message">
    <p v-bind:title="message">{{ message }}</p>
    <p v-bind:title="message2">{{message2}}</p>
    <input v-model="message2">
    <p v-bind:title="message3">{{ message3 }}</p>
</div>


<script>
    function watch(othis, el, key, callbacks) {
        let data = othis.$data;
        let message = data[key]
        // 绑定代理
        porxy(data, key, el, callbacks)
        // 绑定完成后 将数据对象的值 设置到 el 对象上
        data[key] = message;
    }

    function oninput(othis, el, key) {
        // 绑定 input 事件
        let data = othis.$data;
        el.oninput = function (e) {
            // 将 input 输入值设置到数据属性对象上
            // 这里进行设置时将会处理属性 set 方法
            data[key] = e.target.value;
        }
    }

    function porxy(data, key, el, callbacks) {
        // 核心:使用 defineProperty 对属性代理后,进行 属性的 取值 赋值, 实际上调用的是get 、 set方法
        // 代理后 取数据对象的值 其实就是 el.value 的值, 设置取数据对象的值就是 设置 el.value 的值
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get: function () {
                // 代理后 数据对象的值 其实就是 el.value 的值
                value = el.value;
                if (callbacks) {
                    // 循环处理回调对象
                    for (let i = 0; i < callbacks.length; i++) {
                        let callback = callbacks[i];
                        callback.fn(callback.el, callback.key, value);;
                    }
                }
                return value
            },
            set: function (v) {
                // 设置数据对象的值实际就是设置 el.value 的值
                el && (el.value = v);
                // 设置值后进行一次取值,用于触发 其他 el 对象的回调处理方法
                data[key];
            }
        });
    }

    function dataPorxy(othis, data) {
        for (const key in data) {
            if (data.hasOwnProperty(key)) {
                Object.defineProperty(othis, key, {
                    enumerable: true,
                    configurable: true,
                    get: function () {
                        return data[key];
                    },
                    set: function (v) {
                        data[key] = v;
                    }
                });
            }
        }

    }

    function bind(othis, props) {
        // 将数据对象进行绑定代理
        for (const attrValue in props) {
            if (props.hasOwnProperty(attrValue)) {
                const element = props[attrValue];
                watch(othis, element.el, attrValue, element.fn);
            }
        }
        dataPorxy(othis, othis.$data)
    }

    function VBind(param) {
        this.$data = param.data;
        this.el = document.querySelector(param.el);

        othis = this;
        var reg = RegExp('^{{.*}}$');
        let nodeList = this.el.querySelectorAll("*");
        // 回调处理队列
        let props = {};
        nodeList.forEach(function (el, i) {
            if (reg.test(el.innerHTML.trim())) {
                let attrValue = el.innerHTML;
                attrValue = attrValue.replace(/({|})/g, '').trim();
                // 将同名数据对象回调处理放在同一个队列里 fn[]
                // 并将其绑定到到 el 对象
                props[attrValue] || (props[attrValue] = {
                    fn: [],
                    el: null
                });
                // 并将其绑定到到 el 对象
                props[attrValue].el = el;
                // 将回调对象 放入 fn 队列
                props[attrValue].fn.push({
                    fn: function (el, key, v) {
                        el[key] = v;
                    },
                    key: "innerText",
                    el: el
                });
            }
            let attrs = el.attributes;
            for (let i = 0; i < attrs.length; i++) {
                const attr = attrs[i];
                const nodeName = attr.nodeName;
                const attrValue = attr.nodeValue;
                let names = nodeName.split(':');
                switch (names[0]) {
                    case 'v-bind':
                        // 将同名数据对象回调处理放在同一个队列里 fn[]
                        // 并将其绑定到到 el 对象
                        props[attrValue] || (props[attrValue] = {
                            fn: [],
                            el: null
                        });
                        // 并将其绑定到到 el 对象
                        props[attrValue].el = el;
                        // 将回调对象 放入 fn 队列
                        props[attrValue].fn.push({
                            fn: function (el, key, v) {
                                el.setAttribute(key, v);
                            },
                            key: names[1],
                            el: el
                        });
                        break;
                    case 'v-model':
                        // 对 input 对象 绑定 input 事件
                        oninput(othis, el, attrValue);
                        props[attrValue] || (props[attrValue] = {
                            fn: [],
                            el: el
                        });
                        props[attrValue].el = el;
                        props[attrValue].fn.push({
                            fn: function (el, key, v) {
                                el[key] = v;
                            },
                            key: "value",
                            el: el
                        });
                    default:
                        break;
                }
            }
        });
        bind(this, props);
    }
    var b = new VBind({
        el: '#app',
        data: {
            message: 'hello word',
            message2: '双向绑定',
            message3: '绑定到非 INPUT'
        }
    })

    /**
     * 执行下面的页面会自动改变
     * b.message = 'abc'
     * b.message2 = 'abc'
     * b.message3 = 'abc'
     */
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值