zoukankan      html  css  js  c++  java
  • 自己手动实现简单的双向数据绑定 mvvm

    数据绑定

      数据绑定一般就是指的 将数据 展示到 视图上。目前前端的框架都是使用的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();
                    }
                })
            })
        }
    View Code

      

      Watcher

        将对应属性的watcher添加到sub容器中, 当属性变化时,执行更新函数。

        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);
            }
        }
    View Code

      

      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);
        }
    View Code

    到这里, 基本的思路已经实现完毕, 这里只实现了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>

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

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

        

      

      

  • 相关阅读:
    AutoLISP圆内接多边形
    AutoLISP以直线为基线添加文字
    jquery easyuidatagrid 如何清空数据
    MyBatis直接执行SQL查询及批量插入数据
    mappers文件中的#{}语法与${}语法的区别
    在windows7桌面上新建一个快捷方式,指向cmd命令窗口
    ResultSet转换List或直接遍历解决null问题
    linux命令
    flash build 4.6 报错 内存不足,无法加载...
    关于linux安装openoffice无法启动
  • 原文地址:https://www.cnblogs.com/wjyz/p/11419073.html
Copyright © 2011-2022 走看看