原理:通过Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。
1、定义数据:
const vm = new Vue({
el: '#root',
data: {
person:{
name: '张三',
age: 18
}
}
})
2、加工数据:为了实现响应式,vue会给data中的每一组key:value都形成了get和set的写法
只要data中的属性发生改变,就会调用set方法,然后重新解析模板,生成虚拟DOM,新旧虚拟DOM对比,更新页面
3、模拟数据监视:
<script type="text/javascript" >
let data = {
name:'张三',
age:18
}
//创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
//准备一个vm实例对象
let vm = {}
vm._data = data = obs
function Observer(obj){
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
//遍历
keys.forEach((k)=>{
Object.defineProperty(this,k,{
get(){
return obj[k]
},
set(val){
obj[k] = val
}
})
})
}
</script>
这里只能实现一层监视,如果data中还有嵌套的对象,则无效。vue底层做了无限的递归,能够监视data中所有层次的数据。
4、数据代理:
vm._data = data
5、data中已经写好的属性都匹配会匹配get和set(影响页面),之后通过vm._data.person.sex = '男'
这种方法添加的属性,通过delete this.person.name
删除属性是没有响应式的,界面不会更新
6、实现后添加的数据有响应式
语法:Vue.set(target,key,val)
Vue.set(vm._data.person,sex,'男')
// 数据代理
Vue.set(vm.person,sex,'男')
// 或者通过这种方式
vm.$set(vm.person,sex,'男')
注意:
target不能是Vue实例或者实例的根数据对象
// 报错
Vue.set(vm._data,'leader','领导')
Vue.set(vm,'leader','领导')
原理:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
1、定义数据:
const vm = new Vue({
el: '#root',
data: {
hobby:['抽烟','喝酒','烫头']
}
})
2、vue没有为数组里面的某一个元素匹配get、set,如果通过索引去改变值时,数据能改掉,但是Vue监测不到,不会引起页面的更新
3、通过vue封装过了的修改数组方法:push pop shift unshift splice sort reverse可以实现响应式
这些数组的方法被Vue所管理,里面的这些方法不再是正常Array原型对象上的了,它除了可以正常调用Array原型对象上的这些方法外,还能够重新解析模板等一系列操作。
4、Vue.set(vm.hobby,1,'打台球')
也能实现响应式数组修改
vue3的响应式要通过函数是通过函数来实现的
1、作用: 定义一个响应式的数据
2、语法: const xxx = ref(initValue)
xxx.value
{{xxx}}
1、作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref
函数)
2、语法:const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
1、对于基本类型的数据,响应式依然是靠Object.defineProperty()
的get
与set
完成的。
2、对于对象类型的数据
let person = {
name:'张三',
age:18
}
const p = new Proxy(person, {
// 拦截读取属性值
get (target, propName) {
console.log(`有人读取了p身上的${propName}属性`)
// return target[propName]
return Reflect.get(target, propName)
},
// 拦截设置属性值或添加新属性
set (target, propName, value) {
console.log(`有人修改了p身上的${propName}属性`)
// target[propName] = value
Reflect.set(target,propName,value)
},
// 拦截删除属性
deleteProperty (target, propName) {
console.log(`有人删除了p身上的${propName}属性`)
// return delete target[propName]
return Reflect.deleteProperty(target, propName)
}
})
3、Object.defineProperty(target,propName,value) 如果对对象追加一个属性重名了,会直接报错(单线程后面的不执行)
Object.defineProperty(obj,'c',{
get() {
return 3
}
})
Object.defineProperty(obj,'c',{
get() {
return 4
}
})
4、Reflect.defineProperty(target,propName,value) 如果对对象追加一个属性重名了,不会报错。对框架封装比较友好,可以用if…else
const x1 = Reflect.defineProperty(obj,'c',{
get() {
return 3
}
})
console.log(x1) // true
const x2 = Reflect.defineProperty(obj,'c',{
get() {
return 4
}
})
console.log(x2) // false