我们前面学习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类。
监听器(发布者) 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])
}
如果修改arr[2]对象中的name则会发现被劫持了:
{{arr[2].name}}
<button @click='ffff'>改变数组</button>
this.arr[2].name='321'
//this.arr[2]={name:'321'}则仍然和上面一样没有劫持
我们再进一步测试给数组增加一个对象元素并更新真实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])
}
......
- 总结:我们发现对于数组而言,
数组直接元素不会被劫持,如果是引用类型会劫持深层数据,如果后面添加和修改数组的直接元素,那么不会进行任何劫持处理。没有劫持意味着对于数据变化不会引起视图更新。
不过其他变量劫持一旦被触发引起视图更新,所有的
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)等技巧。