MVVM代码实现

192 阅读1分钟

    //第一步  把页面中的 vue 语法 转成正常 的数据
    //  先把模板获取到
    function nodeToFragment($el, vm) {
        let fargment = document.createDocumentFragment();//创建一个文档碎片 为了存储页面上的节点
        let child;
        while (child = $el.firstChild) {
            //  console.log(child);
            compile(child, vm);
            fargment.appendChild(child);//把 child 从 #app 中移动 到文档碎片中
        }
        //通过while 循环 我们把要编译的模板 转移到了文档碎片中   页面上成为空白
        // 下一步我们要编译模板   然后把编译好的模板放到页面上;
        // compile(fargment,vm);//编译模板函数
        $el.appendChild(fargment)
    }
    function compile(node, vm) {
        //这里需要我们判断node 节点是文本节点 3 还是元素节点 1
        if (node.nodeType === 1) {
            //说明是元素节点
            let attrs = node.attributes;//获取node的所以行内属性
            // console.log(attrs);
            [...attrs].forEach(item => {
                if (/v\-/.test(item.nodeName)) {
                    let valN = item.nodeValue;//指令对应的vue 变量名 name  age
                    //    console.log(valN,vm.$data);
                    let val = vm.$data[valN];//vue 变量对应的值
                    //要把val 设置成对应的元素值
                    //node 就是我们对应的元素
                    node.addEventListener('input', (e) => {
                        vm.$data[valN] = e.target.value;
                    }, false);
                    new Watcher(node, vm, valN);
                    node.value = val;//把元素的val设置成 对应的val
                }
            });
            //对该节点继续进行编译 编译该节点的子节点
            // console.log(node);
            [...node.childNodes].forEach(item => {
                compile(item, vm)
            })
            //若没有v-m 指令则 对 该节点 继续进行编译
        } else {
            //文本节点
            //我们要去查找小胡子语法  把小胡子语法对应的变量换成对应的值
            // console.log(node.textContent);//可以获取到对应的文本字符串
            let str = node.textContent;
            node.str = str;//str 是带着小胡子的编译之前的那个值
            if (/\{\{(\w+)\}\}/.test(str)) {
                str = str.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
                    // console.log($1);
                    //只要使用了对应的数据我们就增加一个监听者
                    new Watcher(node, vm, $1);
                    return vm.$data[$1];
                })
                node.textContent = str
            }
        }
    }
    function observe(obj) {
        //执行数据劫持
        let keys = Object.keys(obj)//获取obj中所有的属性名
        keys.forEach(key => {
            //执行 真正的数据劫持
            defineReactive(obj, key, obj[key])
        })
    }
    function defineReactive(obj, key, value) {
        let dep = new Dep();//针对每一个属性各自创造了一个事件池  name 对应一个事件池   
        //name事件池中 都是用到name 节点的那些更新操作
        Object.defineProperty(obj, key, {
            get() {
                console.log(`${key}被使用了`);
                if (Dep.target) {
                    //存储的是对应更新DOM 的操作
                    dep.addSub(Dep.target);
                }
                return value
            },
            set(newValue) {
                if (newValue == value) return;//若 两次数据没改变 就不需要更新DOM
                console.log(`${key}被设置了`);
                value = newValue
                dep.notifty();
            }
        })
    }
    class Dep {  //订阅器
        constructor() {
            this.subs = [];
        }
        addSub(sub) {
            this.subs.push(sub);
        }
        notifty() {
            this.subs.forEach(item => {
                item.update();
            })
        }
    }
    class Watcher {
        //订阅者
        constructor(node, vm, key) {
            Dep.target = this;
            this.node = node;
            this.vm = vm;
            this.key = key;
            this.update();
            Dep.target = null;
        }
        update() {
            this.get(); //this.value  存储的就是更改后的数据的新值
            let str = this.node.str;
            if (str) {
                str = str.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
                    if ($1 == this.key) {
                

                        return this.value;
                    }
                    return this.vm.$data[$1];
                });
                this.node.textContent = str;//把更新完成的str 重新放回页面
            } else {
                //证明是input
                this.node.value = this.value;
            }

        }
        get() {

            this.value = this.vm.$data[this.key];
            console.log(this.value);

        }
    }
    function Vue(options) {
        //  $el 就是我们要操作的元素
        this.$el = document.querySelector(options.el);
        //  $data 中存储的是   对应的vue变量 
        this.$data = options.data || {};
        observe(this.$data);//对数据进行劫持
        nodeToFragment(this.$el, this);//为了把页面中的节点 转移到文档碎片上
    }
    let vm = new Vue({
        el: '#app',
        data: {
            name: '珠峰',
            age: 10
        }
    })