美文网首页
自己手动实现简单的双向数据绑定 mvvm

自己手动实现简单的双向数据绑定 mvvm

作者: 指尖跳动 | 来源:发表于2020-02-27 15:25 被阅读0次

    数据绑定

    数据绑定一般就是指的 将数据 展示到 视图上。目前前端的框架都是使用的mvvm模式实现双绑的。大体上有以下几种方式:

    1. 发布订阅
    2. ng的脏检查
    3. 数据劫持

    vue的话采用的是数据劫持和发布订阅相结合的方式。 而数据劫持用的是Object.defineProperty来实现的, 可以通过绑定get和set来在获取和设置数据的时候触发相应的函数。

    实现

    所以我们需要一个监听器Observe来监听数据的变化。在数据发生变化时,我们需要一个Watcher订阅者来更新视图,我们还需要一个指令的解析器compile来解析指令和初始化视图。

    • Observe 监听器: 监听数据的变化, 通知订阅者
    • Watcher 订阅者: 收到数据的变化, 更新视图
    • Compile 解析器: 解析指令,初始化模板,绑定订阅者
    Observe

    监听数据的每一个属性, 由于可能会有多个watcher,所以需要一个容器来存储。

    function Sub() {
            this.subs = [];
        }
        Sub.prototype = {
            add(sub) {
                this.subs.push(sub);
            },
            trigger() {
                this.subs.forEach(sub => {
                    sub.update();
                })
            }
        };
        Sub.target = null;
    
        function observe(data) {
            if (typeof data !== 'object' || !data) return;
            Object.keys(data).forEach(item => {
                let val = data[item];
                let sub = new Sub();
                Object.defineProperty(data, item, {
                    enumerable: true,
                    configurable: false,
                    get() {
                        if (Sub.target) {
                            sub.add(Sub.target);
                        }
                        return val;
                    },
                    set(newVal) {
                        val = newVal;
                        sub.trigger();
                    }
                })
            })
        }
    
    Watcher
    function Watcher(vm, prop, callback) {
            this.vm = vm;
            this.prop = prop;
            this.callback = callback;
            Sub.target = this;
            let val = this.vm.$data[prop];
            Sub.target = null;
            this.vaule = val;
        }
    
        Watcher.prototype.update = function () {
            let newValue = this.vm.$data[this.prop];
            if (this.value !== newValue) {
                this.value = newValue;
                this.callback.call(this.vm, newValue);
            }
        }
    
    Compile

    获取到dom中的指令和初始化模板, 添加watcher更新视图。

    function Compile(vm) {
            this.vm = vm;
            this.el = vm.$el;
            this.init();
        }
        
        Compile.prototype.init = function () {
            let fragment = document.createDocumentFragment();
            let child = this.el.firstChild;
            while(child) {
                fragment.append(child);
                child = this.el.firstChild;
            }
            let childNodes = fragment.childNodes;
            Array.from(childNodes).forEach(node => {
                if (node.nodeType === 1) {
                    let attrs = node.attributes;
                    Array.from(attrs).forEach(attr => {
                        let name = attr.nodeName;
                        if (name === 'v-model') {
                            let prop = attr.nodeValue;
                            let value = this.vm.$data[prop];
                            node.value = value;
                            new Watcher(this.vm, prop, val => {
                                node.value = val;
                            });
                            node.addEventListener('input', e => {
                                let newVal = e.target.value;
                                if (value !== newVal) {
                                    this.vm.$data[prop] = newVal;
                                }
                            })
                        }
                    })
                }
            
                let reg = /\{\{(.*)\}\}/;
                let text = node.textContent;
                if (reg.test(text)) {
                    let prop = RegExp.$1;
                    let val = this.vm.$data[prop];
                    node.textContent = val;
                    new Watcher(this.vm, prop, val => {
                        node.textContent = val;
                    });
                }
            })
            this.el.appendChild(fragment);
        }
    

    到这里, 基本的思路已经实现完毕, 这里只实现了v-model指令。

    最后,结合 Observe Watcher和 Compile, 就可以成为一个完整的mvvm了。

    <div id="app">
            <div>{{val}}</div>
            <input type="text" id="input" v-model="val">
        </div>
    
    
    <script>
         function MyVue(options) {
            this.$options = options;
            this.$el = options.el;
            this.$data = options.data;
            this.init();
        }
    
        MyVue.prototype.init = function () {
            observe(this.$data);
            new Compile(this);
        };
    
    
        new MyVue({
            el: document.getElementById('app'),
            data: {
                val: 123
            }
        })
    </script>
    

    当然,这只是简单的实现,没考虑细节,主要是学习思路。

    查看效果 (代码直接在页面上)


    from: https://www.cnblogs.com/wjyz/p/11419073.html

    相关文章

      网友评论

          本文标题:自己手动实现简单的双向数据绑定 mvvm

          本文链接:https://www.haomeiwen.com/subject/uezthhtx.html