简单实现一个数据双向绑定MVVM

94 阅读1分钟
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        // 第一步 实现基本架构 MVVM 
        // 第二步 把模型的数据 显示到视图中 M -> V
        // 第三步 更新视图同模型 再更新视图 V -> M
        
        class Vue {
            constructor(options) {
                this.options = options;
                this.$data = options.data; // 获取数据
                this.$el = document.querySelector(options.el); // 获取对象
                this._directives = {}; // 存放订阅者 
                // {myText: [订阅者1,订阅者2], myBox: [订阅者3,...]}

                this.Observer(this.$data);// 
                this.Compile(this.$el);
            }

            // 劫持数据
            Observer(data){
                for(let key in data){
                    this._directives[key] = [];
                    let Val = data[key]; // 当前的值
                    let watch = this._directives[key]; // 得到对应watcher的数组

                    // this.$data 对象中的每一个属性 发生赋值 都要更新视图
                    Object.defineProperty(this.$data, key, {
                        get: function () {
                            return Val;
                        },
                        set: function (newVal) {
                            if(newVal !== Val){
                                Val = newVal;
                                // 遍历订阅者 通知更新视图
                                watch.forEach(function(item){
                                    item.update();
                                })
                            }
                        }
                    })
                }
            }

            // 解析指令
            Compile(el){
                let nodes = el.children; // 获取APP下面的子元素
                for (let i = 0; i < nodes.length; i++) {
                    let node = nodes[i]; // 当前元素
                    if(node.children.length > 0) {
                        this.Compile(node); // 递归循环子节点
                    }
                    if(node.hasAttribute("v-text")){
                        let attrVal = node.getAttribute("v-text");
                        //node.innerHTML = this.$data[attrVal]; 
                        this._directives[attrVal].push(new Watcher(node, attrVal, this, "innerHTML"));
                    }

                    if(node.hasAttribute("v-model")){
                        let attrVal = node.getAttribute("v-model");
                        //node.value = this.$data[attrVal];
                        this._directives[attrVal].push(new Watcher(node, attrVal, this, "value"));
                        node.addEventListener("input",() => {
                            this.$data[attrVal] = node.value;
                        })
                    }
                    
                }
            }
        }

        // 订阅者 更新视图
        class Watcher {
            constructor(el, vm, mySelf, attr){
                this.el = el;
                this.vm = vm;
                this.mySelf = mySelf;
                this.attr = attr;
                this.update(); // 初始化数据
            }
            update(){
                this.el[this.attr] = this.mySelf.$data[this.vm];
            }
        }
    </script>
</head>
<body>
    <div id="app">
        <h1>数据响应式</h1>
        <div>
            <div v-text="myText"></div>
            <div v-text="myBox"></div>
            <input type="text" v-model="myText">
        </div>
    </div>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                myText: "hello world!",
                myBox: "Vue.js",
            }
        })
    </script>
</body>
</html>

学习笔记