写篇文章之必须马上精通vue——(5)Vue2的数据双向绑定

163 阅读4分钟

我们前面学习v-model时发现视图层和数据层互相绑定,其中一方改变另一方也会改变。我们来具体聊聊其中的原理。这里我们讲解V2的原理,V3我后面讲。

本文持续完善补充

Vue的数据双向绑定

vue是MVVM框架,M即model层,V即View层,VM即ViewModel层。VM负责监听和同步M和V的数据。

VM不由我们编写,我们只负责M层的数据处理,然后VM会响应数据变化到V层中。MVC中我们需要自己编写C层代码操作dom手动更新视图。

vue的数据双向绑定基于数据劫持和发布订阅模式,(vue2)具体是Object.defineProperty方法实现setter和getter,并结合Watcher和Observer以及Dep完成监听发布订阅。

实现机制

整个响应式双向数据绑定基于Dep 类、Watcher 类、Observer 类以及Compile类。

image.png

image.png

监听器(发布者) Observer 的实现,主要是指让数据对象变得“可观测”,使用Object.defineProperty

订阅器 Dep 将所有依赖收集起来,是依赖收集容器,订阅/发布模式中定义对象间存在一种一对多的依赖关系,Vue要能够知道一个数据是否被使用,实现这种机制的技术叫做依赖收集。数据变化为“发布者”,依赖对象为“订阅者”,当数据变化的时候 Dep 执行对应订阅者的更新函数。每个对象值的 getter 都持有一个 dep

监听器Observer 是在 get 函数执行了添加订阅者 Wather 的操作的。

解析器 Compile 除了解析模板外,会将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器;

发布/订阅与观察者:  有些地方说观察者模式和发布/订阅模式是一样的,其实是不完全等同的,发布/订阅模式中,其解耦能力更近一步,发布者只要做好消息的发布,而不关心消息有没有订阅者订阅。而观察者模式则要求两端同时存在。

从视图层到数据层,通过事件监听即可实现。

新创建的成员不会绑定

数组劫持

原理预览:vue在做数据劫持时 只劫持了对象和对象中的对象成员名和数组的方法 没有劫持数组的下标。

我们在实例化vue实例的过程中会发现data中数组的直接数组元素没有进行数据劫持

        {{arr[1]}}
        <button @click='fff'>改变数组里面的对象的属性</button>
//this.arr:[10,20,{name:'123'}]
            fff(){
                this.arr[1]='321'//{name:'321'}效果一样
                console.log(this.arr[1])
            }

image.png

如果修改arr[2]对象中的name则会发现被劫持了:

        {{arr[2].name}}
        <button @click='ffff'>改变数组</button>
        this.arr[2].name='321'
        //this.arr[2]={name:'321'}则仍然和上面一样没有劫持

image.png

我们再进一步测试给数组增加一个对象元素并更新真实dom,看是否会绑定:

        <button @click='ffff'>改变数组</button>
        <template v-if='show'>
            {{arr[1].name}}
            <button @click='fffff'>改变</button>
        </template>
......
            ffff(){
                this.arr[1]={name:'xxx'}//增加替换都行
                this.show=true;
            },
            fffff(){
                this.arr[1].name = '----';
                console.log(this.arr[1])
            }
......

image.png

  • 总结:我们发现对于数组而言,数组直接元素不会被劫持,如果是引用类型会劫持深层数据,如果后面添加和修改数组的直接元素,那么不会进行任何劫持处理。没有劫持意味着对于数据变化不会引起视图更新

不过其他变量劫持一旦被触发引起视图更新,所有的data数据就会更新到到视图上,包括没有劫持的数组内的变量。本质上是diff算法和发布订阅模式的是否被执行。

——解决办法

vue框架中提供了一个api用于帮助未被劫持的数据在更新时在视图层响应。我这里自己测试,但是没有展示全部代码,建议大家动手自己尝试。

//this.arr[2] = {name:'xxxx'}这里替换了对象,所以对象内部属性没有劫持
// this.arr[2].name = '----';无法触发视图层修改
// this.arr[2]={name:'----'};无法触发视图层修改
// Vue.set(this.arr[2],'name','----');无法触发视图层修改
Vue.set(this.arr,2,{name:'----'})//成功触发视图层修改

这里我们测试出来的发现即使Vue.set方法也只能对数组直接元素进行视图层更新,更深的数据无能为力。

作为实例的属性之一,数组本身是劫持的,所以我们还可以使用直接重新赋值数组变量本身,通过深拷贝(JSON)等技巧。