Vue中MVVM的实现

Vue的MVVM的实现主要是应用了观察者模式和脏值检查。可以借鉴这张图

图中的含义:

首先自定义一个Vue类(这里取名为Mvue):

/**
 * 入口类
 */
class Mvue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        if (this.$el) {
            //1.实现一个数据观察者
            new Observer(this.$data);
            //2.实现一个指令解析器
            new Compile(this.$el, this);
            this.ProxyData(this.$data);
        }
    }

    ProxyData(data) {
        for (const key in data) {
            Object.defineProperty(this, key, { value: data[key] });
        }
    }
}

然后再实例化一个Observer对象进行属性拦截,设置get()和set()方法,并在Compile对象进行模板编译赋值的时候在Dep(依赖器类)中添加Watcher(观察者类)的引用
Observer类:


/**
 * @description 属性拦截
 */
class Observer {
    constructor(data) {
        this.observer(data);
    }

    observer(data) {
        if (data && typeof data == 'object') {
            Object.keys(data).forEach(key => {
                this.defineReactive(data, key, data[key]);
            });
        }
    }


    defineReactive(data, key, value) {
        this.observer(value);
        const dep = new Dep();
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: false,
            get() {
                Dep.target && dep.addSub(Dep.target);
                return value;
            },
            set: newVal => {
                debugger;
                this.observer(newVal);
                if (newVal != value) {
                    value = newVal;
                    //告知dep通知变化
                    dep.notify();
                }
            }
        });
    }
}

Dep(依赖器类)类:


/**
 * @description 依赖器 添加所有watcher
 */
class Dep {
    constructor() {
        this.watchers = new Array();
    }

    /**
     * @param {object} watch  观察者对象
     */
    addSub(watcher) {
        this.watchers.push(watcher);
    }

    /**
     * 通知更新
     */
    notify() {
        for (const item of this.watchers) {
            item.update();
        }
    }
}

Watcher(观察者)类:

class Watcher {
    constructor(vm, expr, callback) {
        this.vm = vm;
        this.expr = expr;
        this.cb = callback;
        this.oldVal = this.getOldVal();
    }

    /**
     *
     * @param {object} callback 回调更新函数
     */
    update() {
        console.log(this.expr);
        const newVal = compileUtil.getValue(this.expr, this.vm.$data);
        if (newVal !== this.oldVal) {
            this.cb(newVal);
        }
    }

    getOldVal() {
        Dep.target = this;
        console.log(this.expr);
        const oldVal = compileUtil.getValue(this.expr, this.vm.$data);
        Dep.target = null;
        return oldVal;
    }
}

Compile(编译器)类

const compileUtil = {
    /**
     * 编译text
     * @param {node} node
     * @param {string} value
     * @param {object} vm
     */
    text(node, value, vm) {
        let attributevalue;
        if (value.indexOf('{{') !== -1) {
            value.replace(/\{\{(.+?)\}\}/g, (...arg) => {
                new Watcher(vm, arg[1], newValue => {
                    this.updater.textUpdate(node, newValue);
                });
                attributevalue = this.getValue(arg[1], vm.$data);
            });
        } else {
            attributevalue = this.getValue(value, vm.$data);
            new Watcher(vm, attributevalue, newValue => {
                debugger;
                this.updater.textUpdate(node, newValue);
            });
        }

        this.updater.textUpdate(node, attributevalue);
    },
    html(node, value, vm) {
        const attributevalue = this.getValue(value, vm.$data);
        new Watcher(vm, value, newValue => {
            this.updater.htmlUpdate(node, newValue);
        });
        this.updater.htmlUpdate(node, attributevalue);
    },
    model(node, value, vm) {
        const attributevalue = this.getValue(value, vm.$data);
        new Watcher(vm, value, newValue => {
            this.updater.modelUpdate(node, newValue);
        });
        this.updater.modelUpdate(node, attributevalue);
        //添加input事件
        node.addEventListener('input', e => {
            this.setValue(value, vm.$data, node.value);
        });
    },
    on(node, value, vm, eventName) {
        const fn = vm.$options.methods && vm.$options.methods[value];
        node.addEventListener(eventName, fn.bind(vm), false);
    },

    setValue(value, $data, newVal) {
        value.split('.').reduce((data, currentValue) => {
            debugger;
            data[currentValue] = newVal;
        }, $data);
    },

    /**
     * 获取值
     * @param {string} data
     */
    getValue(value, data) {
        return value.split('.').reduce((data, currentValue) => {
            return data[currentValue];
        }, data);
    },

    updater: {
        modelUpdate(node, value) {
            node.value = value;
        },
        textUpdate(node, value) {
            node.textContent = value;
        },
        htmlUpdate(node, value) {
            node.innerHTML = value;
        }
    }
};

class Compile {
    constructor(el, vm) {
        this.vm = vm;
        //获得挂载节点
        this.el = this.IsElementNode(el) ? el : document.querySelector(el);
        //获得文档碎片
        const nodeFragement = this.GetFragementDocument(this.el);
        //划分虚拟文档
        this.compile(nodeFragement.childNodes);
        this.el.append(nodeFragement);
    }

    /**
     *划分节点
     * @param {NodeList} node
     */
    compile(node) {
        for (let nodeItem of [...node]) {
            if (this.IsElementNode(nodeItem)) {
                this.compileElement(nodeItem);
            } else {
                this.compileText(nodeItem);
            }
            if (nodeItem.childNodes && nodeItem.childNodes.length > 0) this.compile(nodeItem.childNodes);
        }
    }
    compileText(node) {
        const content = node.textContent;
        if (/\{\{(.+?)\}\}/.test(content)) {
            compileUtil['text'](node, content, this.vm);
        }
    }
    compileElement(node) {
        [...node.attributes].forEach((attr, index) => {
            const { name, value } = attr;
            if (this.isDirect(name)) {
                //获得指令名称text,html,model...
                const [, directName] = name.split('-');
                //解析事件指令 v-on:click:
                const [dirName, eventName] = directName.split(':');
                compileUtil[dirName](node, value, this.vm, eventName);
            } else if (this.isEvent(name)) {
                const [, eventName] = name.split('@');
                compileUtil['on'](node, value, this.vm, eventName);
            }
            node.removeAttribute(name);
        });
    }

    /**
     * 判断是否是指令
     */
    isDirect(nodeName) {
        return nodeName.startsWith('v');
    }

    isEvent(nodeName) {
        return nodeName.startsWith('@');
    }

    IsElementNode(node) {
        return node.nodeType === 1;
    }

    GetFragementDocument(node) {
        const documentFragement = document.createDocumentFragment();
        let firstChildNode = '';
        while ((firstChildNode = node.firstChild)) {
            documentFragement.append(firstChildNode);
        }
        return documentFragement;
    }
}

很多小伙伴到这里可能有疑惑,Dep在什么时候添加了对Watcher的引用,其实是在第一次初始化的调用CompileUtil 对象中的模板解析方法的时候添加的,当实例化一个Watcher的同时,调用compileUtil中的getValue()方法取出oldValue的值时,调用属性的get()方法添加了对Watcher的引用。当属性发生改变时,使用set()方法调用Dep的notify方法遍历watcher对象并调用update()方法,更新视图。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值