Vue双向绑定实现

前言

vue的双向绑定原理本文就不再赘述了,采用了MVVM模式(Model—View—ViewModel)

clipboard.png

实现思路

首先要有一个Vue类,Vue.data存储着数据,然后采用发布-订阅模式:所有的显示Vue.data数据的标签元素都是一个订阅者,他们都在等待发布者使用notify()方法来通知所有订阅者,然后调用自身的update()方法更新数据。

Vue双向绑定实现_第1张图片

Vue.data中数据变化则要调用notify()方法,所以要进行数据劫持,使用Object.defineProperty()中的set来实现。

Vue双向绑定实现_第2张图片

本文实现v-text与v-model的input:type=text两类标签,前者只能接受数据变化的更新,后者还可以修改存储的数据。因此,我设置了两个订阅者类,一个是只接受更新的父类,另一个则是增加了更改发布者数据的功能。根据单一职责原则,普通观察者并不需要有修改数据的功能,而后者拥有前者的所有属性。采用开闭原则,使用继承来实现功能扩展。被观察者的compile方法是将所有观察者存入列表的一个过程。

Vue双向绑定实现_第3张图片

代码

html部分

包含三个有v-text属性的标签和一个有v-model的input,其中a标签是嵌套于一个div内部的。

vue双向绑定

这里显示data的值

vue类(被观察者类—发布者) 

存储观察者的数据结构采用对象 { 监听的数据名1:[ 观察者1,观察者2,.... ],监听的数据名2:[ 观察者1,观察者2,.... ]  ... }

class Vue {
        constructor(obj) {
            this.data = obj.data();
            this.$el = document.querySelector(obj.el);
            this.observers = {};    //存储观察者的对象
        
            this.notify(this.data);
            this.compile(this.$el);
        }

        // 通知订阅者
        notify(data) {
            for (let key in data) {
                let value = data[key];
                var vm = this;
                // 数据劫持
                Object.defineProperty(data, key, {
                    set(newValue) {
                        value = newValue;
                        vm.observers[key].forEach(item => {
                            item.update(newValue);
                        })
                    },
                    get() {
                        return value;
                    }
                })

            }
        }

        // 绑定观察者
        compile(node) {
            let list = [...node.children];
            list.forEach(el => {
                // 如果该节点有子节点则递归遍历子节点
                if (el.childElementCount) {
                    this.compile(el)
                }
                // 分辨v-text与v-model
                // 并初始化观察者列表
                var attrName;
                if (el.hasAttribute("v-text")) {
                    attrName = el.getAttribute("v-text");
                    if (!this.observers.hasOwnProperty(attrName)) {
                        this.observers[attrName] = [];
                    }
                    // 添加普通观察者
                    this.observers[attrName].push(new Observer(el, this.data[attrName]))
                } else if (el.hasAttribute("v-model")) {
                    attrName = el.getAttribute("v-model");
                    if (!this.observers.hasOwnProperty(attrName)) {
                        this.observers[attrName] = [];
                    }
                    // 添加特殊观察者
                    this.observers[attrName].push(new FormObserver(el, this.data[attrName], this, attrName))
                }

            });
        }
    }

Oberser类——(普通观察者)

 // 观察者类
    class Observer {
        constructor(el, data) {
            this.$el = el;
            this.update(data)
        }

        // 更新数据
        update(data) {
            this.$el.innerText = data;
        }
    }

FormObserver类——(特殊观察者)

比父类多接受两个参数subject与attr,subject是被观察的对象Vue.data,attr是具体的变量名。

// 表单观察者类
    class FormObserver extends Observer {
        constructor(el, data, subject, attr) {
            super(el, data);
            this.subject = subject;
            // 绑定事件
            this.$el.oninput = (function () {
                this.subject.data[attr] = this.$el.value;
            }).bind(this);    //这里面的this原指input
        }
        // 重写函数
        update(data) {
            this.$el.value = data;
        }
    }

创建Vue实例

var vm = new Vue({
        el: "#vue",
        data: function () {
            return {
                data: "123"
            }
        }
    })

效果

Vue双向绑定实现_第4张图片

你可能感兴趣的:(javascript,vue.js)