vue双向绑定的理解

什么是双向绑定

把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新。在单向绑定的基础上,用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定
如:
双向绑定
当用户填写表单时,View的状态就被更新了,如果此时可以自动更新Model的状态,那就相当于我们把Model和View做了双向绑定关系图如下
双向绑定

vue2双向绑定原理:

  • 数据层(Model):应用的数据及业务逻辑
  • 视图层(View):应用的展示效果,各类UI组件
  • 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来

ViewModel
主要职责:
数据变化后更新视图
视图变化后更新数据
当然,它还有两个主要部分组成
监听器(Observer):对所有数据的属性进行监听
解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。通过Object.defineProperty()来实现数据劫持的。

流程图如下:

  1. new Vue()首先执行初始化,对data执行响应化处理,这个过程发生Observe中
  2. 同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile中
  3. 同时定义⼀个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数
  4. 由于data的某个key在⼀个视图中可能出现多次,所以每个key都需要⼀个管家Dep来管理多个Watcher
  5. 将来data中数据⼀旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数
    双向绑定,发布订阅者模式

1.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。(link:v-model)
双向绑定
依赖收集:
双向绑定

2.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
(Object.defineProperty(),该方法有get()和set()方法,set方法,set方法可以劫持到具体更新的那个数据,从而在所有订阅者中找出现在要更新的某一个订阅者
3.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数(watcher),从而更新视图。

class Vue {
    constructor(options) {
        //options.beforeCreate.call(this)   //生命周期:创建前
        this.obj = {};
        this.$data = options.data; //$data接收数据
        this.proxyData(); //将this.$data数据暴露给vue
        //双向绑定第一步:劫持并监听所有属性
        this.observer()
        //options.created.bind(this)() ///生命周期:创建后
        this.$el = document.querySelector(options.el); //绑定,挂载vue根节点
        //options.beforeMount.bind(this)()  //生命周期:挂载前
        this.compile(this.$el);//负责模板解析编译,初始化 ==》view [视图]
        //options.mounted.bind(this)()  //生命周期:挂载后
        console.log(this)
    }

    proxyData() {
        Object.keys(this.$data).forEach(key => {
            //this,当前的,需要暴露
            Object.defineProperty(this, key, {
                get() {
                    //第一次给值,触发改变,那么给监听$data的值就行
                    return this.$data[key];
                },
                set() {
                }
            })
        })
    }

    observer() {
        //监听所有$data的属性
        let vueThis = this;
        Object.keys(this.$data).forEach(key => {
            let item = this.$data[key];
            Object.defineProperty(this.$data, key, {
                get() {
                    return item;
                },
                set(val) {
                    //当该属性发生变化,this.obj[key]就是那个修改了变化
                    //那一个发生变化,触发这个函数
                    //双向绑定第三步:从所有订阅者中,筛选出要更新的订阅者。
                    item = val;//数据发生改变值,再给get,允许改变
                    // new Watcher(this, key, nodes[i], "textContent");
                    vueThis.obj[key].forEach(itemWatcher => {
                        itemWatcher.update();
                    })
                }
            })
        })
    }

    compile(parent) {
        let nodes = parent.childNodes//获取所有的$el内的节点
        for (let i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeType == 1) {//元素节点类型是1
                //判断元素节点上,有没有是双向绑定v-model.
                if (nodes[i].hasAttribute("v-model")) {
                    let key = nodes[i].getAttribute("v-model");
                    nodes[i].value = this[key];//第一次放值
                    nodes[i].addEventListener('input', () => {
                        //改数据
                        this.$data[key] = nodes[i].value;
                        this[key] = nodes[i].value;
                    })
                }
                this.compile(nodes[i])//元素节点里面,可能也有订阅者
            }
            if (nodes[i].nodeType == 3) {//文本节点类型是3
                let text = nodes[i].textContent;
                let newText = text.replace(/{{(.*)}}/g, (firstStr, regStr) => {
                    /*     
                    依赖收集:
                        视图中会用到data中某key,这称为依赖。同⼀个key可能出现多次,每次都需要收集出来用⼀个Watcher来维护它们,此过程称为依赖收集多个Watcher需要⼀个Dep来管理,需要更新时由Dep统⼀通知   
                        双向绑定第二步:添加所有订阅者
                        所有的订阅者:obj格式为 
                    {
                    str:[{},{},{}],//订阅者,在页面3次
                    a:[{}],
                    b:[{}]     
                    }
                    */
                    let key = regStr.trim(),
                        watcher = new Watcher(this, key, nodes[i], "textContent");
                    //判断订阅者,是否多次出现页面
                    if (this.obj[key]) {
                        //有订阅者
                        this.obj[key].push(watcher)
                    } else {
                        //没有订阅者
                        this.obj[key] = []
                        this.obj[key].push(watcher)
                    }
                    return this[key];
                })
                nodes[i].textContent = newText; // ==》view [视图]
            }
        }
    }
    beforeCreate() { }
    created() { }
    beforeMount() { }
    mounted() { }
}
class Watcher {
    // new Watcher(this, key, nodes[i], "textContent");
    constructor(vm, key, node, attr) {
        this.vm = vm;//当前vue
        this.key = key;//vue哪个属性
        this.node = node;//哪个节点
        this.attr = attr;//对节点,哪个属性操作
    }
    update() {
        //更新视图
        // new Watcher(this, key, nodes[i], "textContent");
        this.node[this.attr] = this.vm[this.key];
    }
}

vue2 的双向数据绑定是利用ES5 的一个 API Object.definePropert()对数据进行劫持 结合 发布订阅模式的方式来实现的。
vue3 中使用了 es6 的 ProxyAPI 对数据代理。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值