深入响应式原理
数据响应式举例个来说就是,一个物体对于外界的刺激能够做出相应的反应。使用 Vue进行开发时,一旦页面上的数据在 JS内进行了跟新,不需要开发者修改渲染的代码,Vue会自动进行跟新。
这是因为Vue在实例化声明数据时进行了监听和代理,若数据发生了改变,会监听到变化对此进行重新渲染。
Vue2如何实现数据响应式
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
这个getter/setter对于用户是不可见的,但在内部它们让Vue能够追踪依赖,在 property 被访问和修改时通知变更。
function observe(data){
if(!data || typeof data !== 'object') return
for(let key in data){
let val = data[key]
Object.defineProperty(data,key,{
enumerable:true,
cofigurable:true,
get(){
track(data,key)
}
set(newVal){
trigger(data,key,newVal)
}
})
if(typeof val === 'object'){
observe(val)
}
}
}
function track(data,key){
console.log('get data',key)
}
function trigger(data,key,value){
console.log('set data',key,":",value)
}
var data = {
name:'baizhe',
arr:[1,2,3]
}
observe(data)
console.log(data.name)
data.name = 'valley'
data.arr[0] = 4
data.arr[3] = 5 // ⾮响应式
data.age = 6 //⾮响应式
举例代码如上,通过Object.defineProperty()将a的数据转为 getter/setter。但是观测后,之后新增的属性不会有响应式。若是要对data内声明的数据进行更改。其实实例vm就是data的代理,通过vm可以更改data。
vm.obj.a = 2 // 可以更改
this.obj.a = 2 // 可以更改
this.data.obj.a = 2 // 不可以更改
注意事项
由于JS的限制,Vue对于数组和对象的变化的检查有些问题。
关于对象
- 示例一
new Vue({
data: {},
template: `
<div>{{n}}</div>
`
}).$mount("#app");
n不存在,Vue会给一个警告
- 示例二
new Vue({
data: {
obj: {
a: 0 // obj.a 会被 Vue 监听&代理
}
},
template: `
<div>
{{obj.b}}
<button @click="setB">set b</button>
</div>
`,
methods: {
setB() {
this.obj.b = 1;
}
}
}).$mount("#app");
页面中点击按钮不会出现1,这是因为Vue只会检查第一次属性。
解决方法:
1.在obj内声明b
2.使用Vue.set()/this.$set()
methods:{
setB(){
Vue.set(obj, 'b', 2)
// this.$set(this.obj,'b',2)
}
}
关于数组
Vue不能检测以下数组变动
1.利用索引直接设置一个数组项
2.修改数组长度
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不响应
vm.items.length = 2 // 不响应
解决方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
当数据中含有数组时,若要给数组添加元素,但新增元素的index在声明实例的时候还未定义,为了解决这个问题,Vue的作者将数组的七个方法进行了变更。对Array()函数通过继承,增加了一条原型链的环节,并在环节中添加了七种方法,方法的内部进行了Vue.set()的操作,使的数组的新元素能被实例化。
变更方法
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
- push()
参考来自Vue文档——深入响应式原理
Vue3如何实现数据响应式
Vue3则是通过创建一个对象的代理来实现的,通过Proxy⽤于创建⼀个对象的代理,从⽽实现基本操作的拦截和⾃定义(如属性查找、赋值、枚举、函数调⽤等)。
Reflect是一个内置对象,提供拦截JS操作的方法,这些方法和Proxy handlers相同。
Reflect.set(target, propertyKey, value[, receiver])
将值分配给属性的函数。返回⼀个Boolean,如果更 新成功,则返回true。
Reflect.get(target, propertyKey[, receiver])
获取对象身上某个属性的值,类似于target[name]。
function reactive(obj){
const handler = {
get(target,prop,receiver){
track(target,prop)
const value = Reflect.get(...arguments)
if(typeof value === 'object'){
return reactive(value)
}else{
return value
}
},
set(target,key,value,receiver){
trigger(target,key,value)
return Reflect.set(...arguments)
}
}
return new Proxy(obj,handler)
}
function track(data, key) {
console.log('get data ', key)
}
function trigger(data, key, value) {
console.log('set data', key, ":", value)
}
const people={
name: 'baizhe'
}
const proxy = reactive(people)
proxy.name = 'Tom'
proxy.list = []
proxy.list.push(1) //响应式
Vue3和Vue2响应式原理的区别
前者使用Proxy,后者使用Object.defineProperty。Proxy能劫持整个对象,而Object.defineProperty只能劫持对象的属性;前者递归返回属性对应的值的代理即可实现响应式,后者需要深度遍历每个属性,且对数组操作很不友好。