开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
我们在使用vue的时候,经常会涉及到数据绑定和响应式,这篇文章我们主要就是详细地介绍数据绑定和响应式原理。只有理解透了这些原理,对于开发中常见的一些问题我们就能很快地找到解决方案
数据绑定
数据绑定通俗地讲就是将视图和数据绑定在一起,让页面能够展示正确的数据内容,这里我们就不得不提一下MVVM模型。
1. MVVM模型
1.1 MVVM模型的组成
MVVM模型是有三个部分组成的。
- M: 即Model, 也就是数据层,对应vue中的data
- V: 即View, 也就是视图层,对应vue中的模板
- VM: 即ViewModel, 监听数据的改变和控制视图层的行为,对应vue中的vue实例(vm)
1.2 MVVM模型和MVC模型的区别
MVC模型是Model, View, Controller。这样看它的组成部分和MVVM的差异只是Controller和ViewModel的差异,但是不能理解为是ViewModel替换了Controller,ViewModel只是抽离了Controller中展示的业务逻辑。其中MVC是需要手动获取DOM节点去更新视图层,这样的话页面渲染性能就会很低,加载速度也会变得很慢,而在MVVM中,这些步骤是自动完成的,不需要手动操作DOM节点。
2. 数据绑定
2.1 数据绑定的类型
在vue中,数据绑定分为两种:单向绑定和双向绑定。
2.1.1 单向数据绑定
单向数据绑定只能从数据层到视图层,可以使用v-bind进行绑定
<input type="text" v-bind:value="msg" />
<button @click="ShowMsg()">ShowMsg</button>
data () {
return {
msg: 'hello'
}
},
ShowMsg () {
console.log(this.msg)
}
初始化展示效果
改变视图层的input的输入值,数据层的msg没有任何变化
2.1.2 双向数据绑定
双向数据绑定是可以从数据层到视图层,也可以从视图层到数据层,表单元素一般都是双向数据绑定的,可以使用v-model。
<template>
<div class="hello">
<button @click="changeMsg()">changeMsg</button>
<input type="text" v-v-model:value="msg" />
<button @click="ShowMsg()">ShowMsg</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: 'hello'
}
},
methods: {
changeMsg () {
this.msg = 'hi~'
console.log('数据层传向视图层', this.msg)
},
ShowMsg () {
console.log('视图层传向数据层', this.msg)
}
}
}
</script>
初始化展示效果
改变数据层展示效果
改变视图层展示效果
数据响应式
双向数据绑定的原理的核心就是数据响应式原理。数据响应式指的是数据层改变,视图层也要发生相应的改变,一般在开发的时候,需要特别注意一下引用类型数据的响应式。 数据响应式是通过数据劫持结合发布订阅者模式来实现的,其核心就是Object.defineProperty()。
Object.defineProperty
我们可以使用Object.defineProperty()来实现数据响应式,实现双向数据绑定。下面我们来看一下简单的使用。
1. getter和setter
Object.defineProperty()可以通过getter设置获取对象属性值的操作,setter可以设置修改对象属性值时的操作
let person = {
name: '小仙女',
age: 18
}
let number = 18
Object.defineProperty(person, 'age', {
get () {
console.log('你正在获取person的age值')
return number
},
set (value) {
console.log('你正在修改person的age值')
number = value
}
})
2. 代理
代理的意思就是说对象上没有这个属性值,但是在获取和修改属性值的时候其实操作的是另外一个对象的属性值。
2.1 代理的基本用法
let obj1 = {
x: 10
}
let obj2 = {
y: 14
}
Object.defineProperty(obj1, 'y', {
get () {
console.log('你正在读取obj1的y值')
return obj2.y
},
set (value) {
console.log('你正在修改obj1的y值')
obj2.y = value
}
})
像上面这个例子中,obj1上面是没有y属性的,如果需要获取obj1的y属性值或者设置obj1的y属性值都会代理到obj2,实际上获取和修改的是代理对象obj2的y属性值。
2.2 vue中的代理
<div>{{name}}</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
<script>
const vm = new Vue({
data: {
name: '小仙女',
age: 18
}
})
</script>
从vm的打印信息来看,vm上已经有name和age属性了,但是实际上我们的name和age属性是写在data里面的,也就是vm的_data属性里面的值。
小结:
- vue中的数据代理是通过vm对象来代理data对象中属性的操作(读/写)
- vue中的数据代理的好处:更加方便的操作data中的数据
- 基本原理:
- 通过Object.defineProperty()把data对象中所有的属性添加到vm实例上
- 为每一个添加到vm上的属性,都指定一个getter/setter
- 在getter/setter内部去操作(读/写)data中对应的属性
数据响应式原理
其实真正的数据响应式原理是采用数据劫持结合发布订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变化时发布消息给订阅者,触发相应的监听回调,getter函数里面执行任务的是watcher订阅者,而setter函数执行任务的是发布者。
发布-订阅者模式
前面我们介绍到数据响应式原理是采用数据劫持结合发布订阅者模式的,那么发布订阅者模式到底是什么呢?在理解这个模式之前需要先理解一下观察者模式。
观察者模式是对象间一对多的关系,当一个对象发生改变,所有依赖于它的对象都需要改变。
发布订阅者模式是观察者模式的一种别称,现在的发布订阅者模式也可以看成是一种升级版,它不再是通过发布者直接将消息事件发布给订阅者,而是中间多了一层调度中心,也就是说发布者把消息发布到订阅中心,订阅中心将消息事件分发给订阅者,发布者和订阅者之间是不直接联系的。如果把以前的观察者模式称之为报社和订报人之间的关系(订报人跟报社说要订报纸,当报纸印发好了就会通知订报人,然后给订报人送报纸),那么现在的发布订阅模式就是博主和粉丝之间的关系(当粉丝关注博主A之后,如果后期博主A发布动态,微博就相当于是调度中心,将博主A的动态分发给对应的粉丝)。可以用下图展示。
发布订阅者模式的好处就是面向调度中心编程的,这样的话就可以解耦发布者和订阅者。
实现方式
- 实现一个数据监听器Observer, 能够对数据对象的所有属性进行监听,当对象的属性发生变化时,能够拿到最新值并且通知给订阅者。
- 实现一个指令解析器Compile, 对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,并且绑定相应的更新函数。
- 实现一个Watcher, 作为连接Observer和Compile的中间桥梁,能够订阅并收到每个对象属性变动的通知,执行指令绑定的相应回调函数,从而达到更新视图的目的。(相当于是发布订阅者模式中的调度中心)
- MVVM入口函数,整合以上三者