Vue响应式原理Object.defineProperty这样理解(二)

在这里插入图片描述
输入框内输入外面内容有人修改
在这里插入图片描述
控制台修改message内容
在这里插入图片描述
在这里插入图片描述

<div id="app">
    <input type="text" v-model="message">
    {{message}}
</div>
<script>
    const app = new Vue({
        el:'#app',
        data: {
            message: '哈哈',
        }
    })
</script>

我们不引入Vue直接写ES6代码,我们new了一个vue就要写一个vue的类
在这里插入图片描述

<script>
    class Vue {
        constructor(options) {
            //1.保存数据
            this.$options = options;
            this.$data = options.data;
            this.$el = options.el;

            //2.将data添加到响应式系统中
            new Observer(this.$data);

            //3.代理this.$data的数据
            Object.keys(this.$data).forEach(key => {
                this._proxy(key)
            });

            //4.处理el
            new Compiler(this.$el, this)
        }

        _proxy(key) {
            Object.defineProperty(this,key,{
                configurable: true,
                enumerable: true,
                set(newValue) {
                    this.$data[key] = newValue
                },
                get() {
                    return this.$data[key]
                }
            })
        }
    }

模块介绍

https://www.php.cn/js-tutorial-390322.html

Observer

遍历data中的所有属性,同时通过递归的方式遍历这些属性的子属性,通过Object.defineProperty()将这些属性都定义为访问器属性,访问器属性自带get和set方法,我们通过get和set方法来监听数据变化。

    class Observer {
        constructor(data) {
            this.data = data;

            Object.keys(data).forEach(key => {
                this.defineReactive(this.data, key, data[key])
            })
        }

        defineReactive(data, key, val) {
            //一个属性Key对应一个Dep对象
            const dep = new Dep();
            Object.defineProperty(data, key,{
                enumerable: true,
                configurable: true,
                get() {
                    if (Dep.target) {
                        dep.addSub(Dep.target)
                    }
                    return val
                },
                set(newValue) {
                    if (newValue === val) {
                        return
                    }
                    val = newValue;
                    dep.notify()
                }
            })
        }
    }
Dep

我们需要消息订阅器来收集所有的订阅者,就好像是一个列表,当属性的get方法被触发时,需要判断是否要添加订阅者,如果需要就在列表中增加一个订阅者,当set方法被触发时就通知列表中所有的订阅者来做出响应。

    class Dep {
        constructor() {
            this.subs = []
        }

        addSub(sub) {
            this.subs.push(sub)
        }

        notify() {
            this.subs.forEach(sub => {
                sub.update()
            })
        }
    }
Watcher

因为我们是在get函数中判断是否要添加订阅者的,要想把一个订阅者添加到列表中我们就需要在初始化这个订阅者时触发get函数,我们可以在Dep.target上缓存下订阅者,添加成功后再将其去掉就可以了。\

    class Watcher {
        constructor(node, name, vm) {
            this.node = node;
            this.name = name;
            this.vm = vm;
            Dep.target = this;
            this.update();
            Dep.target = null;
        }

        update() {
            this.node.nodeValue = this.vm[this.name]
        }
    }
Compile

compile负责初始化时的编译解析,遍历每一个结点,看哪些结点需要订阅者,也负责后续为订阅者绑定更新函数。

    const reg = /\{\{(.+)\}\}/;
    // .匹配任何内容(除了特殊的符号)
    // * 0或多个
    // + 1或多个
    // {}在正则中有特殊的含义
    // regular正规   expression表达式

    class Compiler {
        constructor(el, vm) {
            this.el = document.querySelector(el);
            this.vm = vm;

            this.frag = this._createFragment();
            //这里我们用到的是_createFragment返回的frag,这样就不会被移除了
            this.el.appendChild(this.frag)
        }

        _createFragment() {
            const frag = document.createDocumentFragment();

            let child;
            //遍历每一个children结点
            while(child = this.el.firstChild){
                this._compile(child);
                //结点放到frag里
                frag.appendChild(child);
            }

            return frag
        }

        _compile(node) {
            if (node.nodeType === 1) {//标签节点
                const attrs = node.attributes;
                if (attrs.hasOwnProperty('v-model')) {
                    const name = attrs['v-model'].nodeValue;
                    node.addEventListener('input', e => {
                        this.vm[name] = e.target.value;
                    })
                }
            }
            if (node.nodeType === 3) {//文本节点
                console.log(reg.test(node.nodeValue));
                if (reg.test(node.nodeValue)) {
                    const name = RegExp.$1.trim();
                    console.log(name);
                    new Watcher(node, name, this.vm)
                }
            }
        }
    }
</script>

数据双向绑定原理

参考链接:https://github.com/DMQ/mvvm

数据劫持: vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
总的来说,就以下几个步骤:

  1. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
  2. 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  3. 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
  4. mvvm入口函数,整合以上三者
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值