本文已参与【新人创作礼】活动,一起开启掘金创作之旅。
前言:Vue2实现响应式原理使用的是
Object.defineProperty()
对对象中某个属性的读取、修改进行拦截,存在新增和删除缺陷。而Vue3实现响应式原理使用的是Proxy
代理,拦截对象中任意属性的变化;
一、回顾Vue2响应式原理
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。 存在问题:
-
当对象中属性过多时Object.defineProperty()需针对每个属性进行遍历实现响应式,效率不高;
-
新增属性、删除属性, 界面不会更新;
-
只有configurable为true时候,该属性才能从对应的对象上被删除,但源数据不会响应删除;
-
直接通过下标修改数组, 界面不会自动更新。
<script>
// 源数据
let person = {
name: '张三',
age: 18
}
let p = {}
// 对源数据中的每个属性进行遍历,使其变为响应式
Object.defineProperty(p, 'name', {
// **只有configurable为true时候,该属性才能从对应的对象上被删除,但person不会响应删除**
configurable:true,
get() {
return person.name
},
set(value) {
person.name = value
}
})
Object.defineProperty(p, 'age', {
get() {
return person.age
},
set(value) {
person.age = value
}
})
</script>
新增属性代码运行演示:
删除属性代码运行演示:
二、Vue3响应式原理
实现原理:
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
-
MDN文档中描述的Proxy与Reflect:
<script>
// 源数据
let person = {
name: '张三',
age: 18
}
Proxy和Reflect是window上内置的函数
let p = new Proxy(person, {
get(target, propName) {
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target, propName)
},
set(target, propName, value) {
console.log(`有人新增/修改了p身上的${propName}属性,我要去更新界面了!`)
Reflect.set(target, propName, value)
},
deleteProperty(target, propName) {
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return Reflect.deleteProperty(target, prop)
}
})
</script>
下面来详细介绍下Proxy(代理)和Reflect(反射):
Proxy:
- 语法:
let p = new Proxy(target, handler)
- target:要使用
Proxy
包装的目标对象,此处为person源数据; - handler:一个对象。可以只传一个空对象,也能实现增删改查操作
let p = new Proxy(person, {})
;可以向上述代码一样传入含有的get
set
deleteproperty
函数来拦截对象中任意属性的变化;
- target:要使用
get函数:
get(target, propName) {
// target:目标对象,也就是person源数据;
// propName:被获取的属性名;
console.log(`target, propName`, target, propName)
return target[propName]
},
set函数:
// set函数对新增或修改一个属性都可以拦截到
set(target, propName, value) {
// value:新属性值
console.log(`target, propName,value`, target, propName,value)
target[propName] = value
},
deleteProperty函数:
deleteProperty(target, propName) {
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return delete target[propName]
}
有人注意到,我在此处展示函数阶段没有用到Reflect函数,但最开始展示原理时候却是用到的,以下解释一下Reflect作用。
Reflect:
Reflect函数身上的一些方法与Object相同,比如:
Reflect.get(target,propName)
相当于get函数中直接target[propName]
;Reflect.set(target,propName,value)
相当于set函数中target[propName] = value
;Reflect.deleteProperty(target,propName)
相当于deleteProperty函数中的delete target[propName]
当然,Reflect函数身上还有Reflect.defineProperty
方法,与Object.defineProperty有所不同:Object.defineProperty
对同一个对象同一个属性重复操作时,系统会报错代码运行不下去,但Reflect.defineProperty
不会报错只会运行第一条结果并继续执行后边代码;
Object.defineProperty:
let obj = { a: 1, b: 2 }
Object.defineProperty(obj, 'c', {
get() {
return 3
}
})
Object.defineProperty(obj, 'c', {
get() {
return 4
}
})
Reflect.defineProperty:
let obj = { a: 1, b: 2 }
const x1 = Reflect.defineProperty(obj, 'c', {
get() {
return 3
}
})
console.log('x1',x1)
const x2 = Reflect.defineProperty(obj, 'c', {
get() {
return 4
}
})
console.log('@@@@@')
- 以上代码演示可以看出,
Reflect.defineProperty
拥有返回值,且是一个布尔值,为后续逻辑判断做铺垫,不用向Object.defineProperty
一样进行try...catch
捕获。
注:一般组件封装用Reflect函数比较多,宽容度好,组件更健壮