1.Vue2中存在的问题
1.对象直接添加新的属性或删除已有属性,界面不会自动更新,不是响应式。
2.数组直接通过下标修改元素,比如(arr[1]=xxx)或者更新数组的length,界面不会自动更新,不是响应式的。
<template>
<div class="demo_box">
<div class="df" v-for="(value, key) of person" :key="key">{{ key }}--{{ value }}</div>
<button class="df" @click="add">新增对象属性</button>
<div class="df" v-for="(item, index) of arr" :key="index">{{ item }}</div>
<button class="df" @click="modify">修改对象数组元素</button>
</div>
</template>
<script>
export default {
data() {
return {
person: {
name: "小储",
age: 18,
},
arr:[
'小明','小王','小狗'
]
};
},
methods:{
add(){
this.person.className='五班'
console.log(this.person,1)
},
modify(){
this.arr[2]='小猫'
console.log(this.arr,22)
}
}
};
</script>
<style scoped>
.df{
color: red;
}
.demo_box{
width: 100%;
margin: 0 auto;
text-align: center;
}
</style>
从打印的结果可以看出,对象和数组已经改变了,但是页面不更新。
2.Vue2响应式原理
(1)对象
- 核心:通过defineProperty对对象已有属性值的读取和修改进行劫持(监视/拦截
const vm={}//vm其实就是我们正常vue2里的vue实例
const data={
name:'储',
age:18
}
//遍历data,将Data里的属性绑定到vm上去,对属性值的读取和修改进行拦截
//Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,
Object.entries(data).forEach(([prop,value])=>{
let initValue=value;
Object.defineProperty(vm,prop,{
get(){
console.log('执行get')
return initValue
},
set(newValue){
console.log('执行set')
initValue=newValue
}
})
})
//读取属性值
console.log(vm.name)//打印结果: '执行get' '储'
//修改属性值
vm.age=27//执行set
console.log(vm.age)//打印结果: '执行set' 27
从例子可以看出当修改读取时可以被监听劫持到的,但是当我们新添加属性和删除属性时却监听劫持不到
//新添加属性
vm.sex='女'
//删除属性
delete vm.name
这就是vue2对象响应式的弊端。还需要通过$set或者强制刷新.
(2)数组
- 核心:通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
数组的push,pop,reverse,splice,shift,unshift ,sort这七种方法之所以可以正常使用,其实是因为被vue重写了。
//把push,pop等方法放在一个对象里面
const obj={
pop(){},
push(){},
shift(){},
unshift(){},
splice(){},
sort(){},
reverse(){}
}
console.log(Object.keys(obj),2222)// ['pop', 'push', 'shift', 'unshift', 'splice', 'sort', 'reverse'] 2222
//遍历obj,使用defineProperty拦截监听
Object.keys(obj).forEach(key=>{
Object.defineProperty(obj,key,{
value:function(...args){
console.log(...args,this,'ds')//this指向arr 打印结果:1 [] 'ds'
return Array.prototype[key].call(this,...args)
}
})
})
const arr=[]
arr.__proto__=obj;//将数组的隐式原型指向obj
//测试
arr.push(1)
console.log(arr)
从代码可以看出arr数组可以使用push方法主要是因为 当我们定义arr=[]时,其实它相当于arr=new Array(),所以arr指向Array的原型函数,即arr.__proto__=Array.prototype,所以arr可以使用数组的push等方法,但是现在我们把arr.__proto__又等于obj了,所以arr.push就相当于obj.push了,再obj.push我们用defineProperty 进行了监听,执行obj.push()方法就会执行value函数。
代码里的Array.prototype打印出来有如下些内容
Array.prototype[key].call(this,...args)这段代码意思是用 Array.prototype[key]来替换this,例子里this指的是arr
3.Vue3的响应式
- 核心:
- 通过Proxy(代理):拦截对对象本身的操作,包括属性值的读写,属性的添加,属性的删除等....
- 通过Reflect(反射):动态对被代理对象的相应属性进行特定的操作
(1)对象
const user={
name:'小储',
age:18
}
//代理对象
const proxyUser=new Proxy(user,{
get(target,prop){
console.log('劫持get()',prop)
return Reflect.get(target,prop)
},
set(target,prop,val){
console.log('劫持set()',prop,val)
return Reflect.set(target,prop,val)
},
deleteProperty(target,prop){
console.log('劫持delect',prop)
return Reflect.deleteProperty(target,prop)
}
})
//读取属性值
console.log(proxyUser===user)//false
console.log(proxyUser.name)//劫持get() name '小储'
//设置属性值
proxyUser.name='小储储'//劫持set() name 小储储
//新增/删除属性
proxyUser.className='五班'
delete proxyUser.age
打印的结果如下:
可以看出对象新增属性修改属性都会被set劫持,读取属性会被get劫持,删除属性会被deleteProperty劫持
(2)数组
和对象一样
const user=[1,2,3]
//代理对象
const proxyUser=new Proxy(user,{
get(target,prop){
console.log('劫持get()',prop)
return Reflect.get(target,prop)
},
set(target,prop,val){
console.log('劫持set()',prop,val)
return Reflect.set(target,prop,val)
},
deleteProperty(target,prop){
console.log('劫持delect',prop)
return Reflect.deleteProperty(target,prop)
}
})
//读取
proxyUser[0]
//通过下标修改
proxyUser[0]=4
打印结果如下:
通过例子可以看出vue3已经没有了vue2的响应式弊端了。