前情提要:
数据交互的方式:
1、声明式
只需要声明在哪里where,做什么what,而无需关系如何实现how
//此map方法就是,声明式的,只告诉map要实现获取2倍,然后map具体怎么实现不关心
var arr = [1,2,3,4];
var newArr = arr.map((item,index)=>{
return item*2;
})
2、命令式
需要以具体的代码表达在哪里where,做什么what,以及如何实现how
//forEach就是命令式,会具体操作数组,还有如何得到结果push
var arr = [1,2,3,4];
var newArr = [];
arr.forEach((item,index)=>{
newArr.push(item*2);
})
声明式的理解:
1)DOM状态只是数据状态的一种映射
2)所有的逻辑尽可能在状态的层面去进行
3)当状态变化了,View会被框架自动更新到合理的状态
正文
Vue响应式原理
什么是响应式?
<div id="app">
<div>Price :¥{{ price }}</div>
<div>Total:¥{{ price * quantity }}</div>
<div>Taxes: ¥{{ totalPriceWithTax }}</div>
<button @click="changePrice">改变价格</button>
</div>
var app = new Vue({
el: '#app',
data() {
return {
price: 5.0,
quantity: 2
};
},
computed: {
totalPriceWithTax() {
return this.price * this.quantity * 1.03;
}
},
methods: {
changePrice() {
this.price = 10;
}
}
})
上例中当price 发生变化的时候,Vue就知道自己需要做三件事情:
- 更新页面上price的值
- 计算表达式 price*quantity 的值,更新页面
- 调用totalPriceWithTax 函数,更新页面
数据发生变化后,会重新对页面渲染,这就是Vue响应式,那么这一切是怎么做到的呢?
- 侦测数据的变化 (数据劫持 / 数据代理)
- 收集视图依赖了哪些数据 (依赖收集)
- 数据变化时,自动“通知”需要更新的视图部分,并进行更新(发布订阅模式)
Vue响应式基本概念:
Vue 的响应式,是指当数据改变后,Vue 会通知到使用该数据的代码;例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
- 其核心机制是 观察者模式,我们把依赖数据的观察者称为 watcher;
- 数据可以有多个观察者,怎么记录这种依赖关系呢?
Vue 通过在data 和 watcher 间创建一个 dep 对象,来记录这种依赖关系; - dep 的结构很简单,除了唯一标识属性id,另一个属性就是用于记录所有观察者的 subs:
- id - number
- subs - [Watcher]
1.语法:
Object.definProperty(obj,prop,descriptor)
2.参数:
obj是要在其上定义属性的对象;
prop要定义的或要修改的属性的名称;
descriptor将被定义或修改的属性描述符;
3.数据描述:
configurable:是否可以删除属性,默认false;
enumberable:此属性是否可以被枚举,默认false;
value:该属性对应的值,默认undefined;
writable:属性的值是否可以被重写,默认false;
4.访问器描述:
getter:是一种获得属性值的方法;
setter:是一种设置属性值的方法;
数据变化的过程
- 侦测数据的变化 (数据劫持 / 数据代理)
- 收集视图依赖了哪些数据 (依赖收集)
- 数据变化时,自动“通知”需要更新的视图部分,并进行更新(发布订阅模式)
追踪数据
在 new Vue() 后, Vue 会调用 _init 函数进行初始化,也就是init 过程,在 这个过程Data通过Observer转换成了getter/setter的形式,来对数据追踪变化,当被设置的对象被读取的时候会执行getter函数,而在当被赋值的时候会执行setter函数。
- 也就是说,当创建Vue实例时,Vue会将这个实例的data对象转换成getter/setter形式,这对用户是不可见的。
- 所以就出现了无法进入响应式系统的数据,稍后介绍。
收集依赖
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据property记录为依赖。之后当依赖项的 setter触发时,会通知watcher,从而使它关联的组件重新渲染。
- 当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中。
订阅者
主要作用是用来存放 Watcher观察者对象
class Dep {
constructor () {
/* 用来存放Watcher对象的数组 */
this.subs = [];
}
/* 在subs中添加一个Watcher对象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Watcher对象更新视图 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
- 在修改对象的值的时候,会触发对应的setter, setter通知之前依赖收集得到的 Dep 中的每一个Watcher,告诉它们自己的值改变了,需要重新渲染视图。这时候这些Watcher就会开始调用update来更新视图。
图解全过程
响应式注意项 - 对于对象来说
- 添加或者删除属性,那么这个属性是无法进入响应式系统的
- 覆盖对象是可以进入响应式系统的
- 解决方法可以用Vue.set()或是他的别名$set()
const app = new Vue({
el: '#app',
data: {}
})
app.msg = 'hello' //不是响应式
app.$set(app, 'msg', 'hello') //响应式
- 对于数组来说
- 改变数组的长度
- 根据索引值直接赋值
- vue响应式原理理解:
const app = new Vue({
el: '#app',
data: {
arr: [1, 2, 3, 4]
}
})
app.arr[1] = 3333 //不是响应式
app.$set(app.arr, 1, 3333) //是响应式
app.arr.length = 3 //不是响应式
app.arr.splice(3) //是响应式
- 声明响应式: property 由于 Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式property,哪怕只是一个空值
- Vue 在更新 DOM 时是异步执行的,为了在数据变化之后等待 Vue 完成更新DOM,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。
- Vue.nextTick(callback)或实例方法app.$nextTick(callback)
- $nextTick()返回一个Promise对象,所以你可以使用新的 ES2017async/await 语法完成
this.title = ''
this.$nextTick(() => {
alert(this.title)
})
this.title = 123
//alert(123)
声响应式数据的理解:
1、把一个普通的js对象传给Vue实例的data选项对象
2、Vue将遍历此对象的所有的属性,并使用Object.defineProperty把这些属性全部转换为getter/setter
3、Vue内部会对数据进行劫持操作,进而追踪依赖,在属性被访问和修改时通知变化
参考文档:
https://www.cnblogs.com/fundebug/p/responsive-vue.html
https://blog.csdn.net/qq_44888570/article/details/114070909