视图更新(响应式原理)


使用数据劫持配合观察者模式、发布订阅模式运行响应式

/**
 * 仿写一个简单的Vue.js
 * Vue使用层上是数据驱动,而源码上数据更新了,dom也要更新
 */

class Vue {

    //构造器
    constructor(options) {

        this.$options = options;

        //观察者监听事件
        this.$watchEvent = {};

        //观察者 模板更新
        this.observer();

        //生命周期函数created
        if (Object.prototype.toString.call(options.created) === "[object Function]") {
            options.created.call(this);
        }

    };

    /**
     * 编译
     * 对模板变量
     */
    compile(node) {

        node.childNodes.forEach((item, index) => {

            //元素节点
            if (item.nodeType === 1) {

                //事件监听
                if (item.hasAttribute("@click")) {

                    let vmKey = item.getAttribute("@click");

                    //做函数监听
                    if (Object.prototype.toString.call(this.$options.methods[vmKey]) === "[object Function]") {
                        item.addEventListener('click', (event) => {
                            this.$options.methods[vmKey].call(this, event);
                        });
                    }

                }

                //递归
                this.compile(item);
            }

            //文本节点
            if (item.nodeType === 3) {

                //正则
                let reg = /\{\{(.*?)\}\}/g;

                //textContent属性表示一个节点及其后代的文本内容。
                let text = item.textContent;

                //编写规则match匹配字符 vmkey是匹配到的data属性
                item.textContent = text.replace(reg, (match, vmKey) => {
                    //去掉两边空格
                    vmKey = vmKey.trim();

                    //给模板匹配到的每一个模板变量创建监听对象
                    let watch = new Watch(this, vmKey, item, "textContent")

                    //判断有没有这个属性,没有则不需要进行监听
                    if (this.hasOwnProperty(vmKey)) {
                        if (this.$watchEvent[vmKey]) {
                            this.$watchEvent[vmKey].push(watch);
                        } else {
                            //初始化监听
                            //由于一个data可能在模板上使用多次,那么应该使用一个数组存储
                            this.$watchEvent[vmKey] = [];
                            this.$watchEvent[vmKey].push(watch);
                        }
                    }

                    //替换模板
                    return this.$data[vmKey]

                })
            }

        })
    };



    /**
     * 观察者
     * 对this.$data的变化进行监听,更新模板
     */
    observer() {
        for (let key in this.$data) {
            let value = this.$data[key];
            let that = this;
            Object.defineProperty(this.$data, key, {
                get() {
                    return value;
                },
                set(newVal) {
                    value = newVal;

                    //判断是否有这个属性的监听对象,意味着这个data属性是否在模板中被使用了
                    if (that.$watchEvent[key]) {

                        //其value是一个数组,因为data属性可能在模板中使用了不止一次,这是在compile的使用push的
                        for (let val of that.$watchEvent[key]) {
                            val.update();
                        }
                    }
                }
            })
        }
    }

    
}


/**
 * 监听对象
 */
class Watch {
    constructor(vm, key, node, attr) {
        //vue实例对象
        this.vm = vm;
        //属性名称
        this.key = key;
        //节点 
        this.node = node;
        //节点属性如textContent
        this.attr = attr;
    };

    //更新模板
    update() {
        //将最新的vm data给到节点属性
        this.node[this.attr] = this.vm[this.key];
    }
}

文章作者: iamfugui
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 iamfugui !
评论
  目录