hello,我是小索奇哈,精心制作的Vue系列持续发放,涵盖大量的经验和示例,由浅入深进行讲解。 本章给大家讲解的是数据监视,前面的章节已经更新完毕,后面的章节持续输出,有任何问题都可以留言或私信哈,一起加油~
Vue实现数据监测的核心是通过defineProperty()劫持属性的getter& setter,当我们获取data数据时,底层都是通过调用getter & setter来实现的,在属性读取或修改时可以进行额外操作这实际上将数据对象“代理”了一层,形成所谓的“响应式数据”
具体一点来说哈,在初始化组件时,Vue会遍历data对象的所有属性,使用Object.defineProperty将它们转换成getter/setter这种代理允许Vue追踪依赖,在属性被访问和修改时通知变化(VM中的data就数据代理自_data)
例如我们定义了一个名为message的data属性,Vue会将其转化为
Object.defineProperty(data, 'message', {
get () {
// ...进行依赖收集
},
set (newValue) {
// ...触发更新
}
})
这样在读取或修改message时,就可以触发getter和setter,从而进行依赖收集和更新触发,把这些直接叫做数据劫持即可
数据劫持就是Vue实现响应式的基石,它可以检测数据变化并触发回调来完成视图更新,使开发者只需要关注数据本身而不需要手动操作DOM
这里为什么提到数据劫持呢?
一句话形容:数据劫持是手段,数据监视是目的(没有数据劫持,就无法精确监视数据变化)
具体往下看,这里划重点,结合下面,不懂再爬上来一下哈
模拟一个小响应系统的工作方式:
这个响应式系统,可以自动更新视图
当获取属性值时,收集订阅者;当设置属性值时,通知订阅者更新视图
没有get和set就不会有响应式数据,也不会显示到页面上!(调试页面中的带括号的就证明有,直接定义的没有get、set也不会响应到页面上)
拓展
响应式转换可能不理解?
// 响应式转换
const reactiveData = reactive(data);
reactive函数是用来把普通对象转换成响应式对象的,它接受一个普通对象作为参数,对这个对象的所有属性进行响应式处理,然后返回这个响应式对象
所以这里
const data = {
name: 'John',
age: 20
};
是定义一个普通对象data
调用:
const reactiveData = reactive(data);
reactive函数会遍历data对象的所有属性,使用Object.defineProperty()把这些属性转换成getter/setter的形式
这样responsiveData就变成一个响应式对象了,它和data对象具有相同的属性,但多了响应式的功能之后我们使用responsiveData来代替data,就可以实现视图的自动更新
简单来说reactive(data)这一行的作用就是把一个普通对象data转换成响应式对象reactiveData,得到一个可以实现数据监听与视图更新的响应式数据对象
需要注意的是,由于是直接监测属性,所以对象上的层级结构过深时,内部属性的变化不会触发响应这需要完整替换对象或手动设置新值
data: {
user: {
name: 'John',
friend: {
name: 'Chris'
}
}
}
如果直接这样改friend.name,Vue监测不到(直接修改深层属性无法被监测到,是因为Vue的响应式通过劫持属性的getter和setter来实现的但它只能劫持对象的第一层属性~)
this.user.friend.name = '小索奇'
重要:必须整个替换user对象,或者用Vue.set | this.$set 改friend.name,才会是响应式的数据,才会生效!
// 非响应式
this.user.name = '小索奇'
// 响应式
// Vue.set(target, key, value) | this.$set(target, key, value)
this.$set(this.user.friend, 'name', '小索奇')
数组也一样,记住不能够通过数组的索引值更改(数组本身不具备get、set方法)
this.list[0] = 'A'
// 如果数组下有对象属性,可以更改
this.list[0].name = '小索奇'
要直接调用Vue的全局API来改变数组,如用Vue.set改索引,或者用数组自身push、pop、shift、unshift、splice、sort、reverse、之类的API
这些方法都被Vue做了包装,所以可以进行视图更新
上面的就可以用splice替换
this.list.splice(start, deleteCount, item1)
异步函数里改数据,页面也不会立刻更新,只有等异步函数完了,页面才显示新数据
setTimeout(() => {
this.message = 'hello'
}, 1000)
所以用定时器、ajax之类的改数据,页面会晚一点才刷新
这就是Vue数据监测需要注意的几个点概括来说就是:
嵌套深的数据要用Vue.set或者整个替换
数组索引要用Vue.set或数组方法改
异步函数里改数据,页面更新会 delay 一下
拓展
Vue.set(target,key,val)
// vm.$set()
Vue.set(vm._data.obj,'country','China')
// 为什么等同于下面的
Vue.set(vm.obj,'country','China')
这是因为数据代理-review
Vue.set
方法可以向响应式对象中添加一个属性并确保这个新属性同样是响应式的
Vue 在初始化时,会将 data
对象代理到 Vue 实例上,所以 vm.obj
等同于访问 vm._data.obj
也就是说,在组件实例 VM
中,访问 vm.obj
等同于访问 vm._data.obj
,因为它会被代理到 _data
中的原始数据对象上
这是通过 Vue 的 proxy
方法实现的,大致如下:
function proxy(vm) {
Object.keys(vm._data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm._data[key];
}
});
});
}
所以 Vue.set
方法既可以接受原始的 _data
对象,也可以接受代理后的组件实例对象,效果是相同的
它们都指向同一个原始数据对象
注意
target不允许是VM实例,也不允许是直接根对象,比如data
image-20230823005727583