vue3.0 升级内容
全部用TS重写的(响应式、vdom、模本编译)
性能提升,减少代码量
会调整部分API
Proxy重写响应式
vue2.x 马上要过时了吗
vue3.0从正式发布到推广,还需要一段时间
vue2.x应用范围广,有大量项目需要维护升级
proxy存在兼容性问题,且不能ployfill
社区热门知识点:Proxy重写响应式讲解
回顾vue2.*的响应式原理 [object.defindeProperty]
object.defindeProperty缺点:
- 深度监听需要一次性递归
- 无法监听新增属性/删除属性(vue.set/vue.delete)
- 无法原生监听数组,需要特殊处理
vue3_Proxy实现响应式原理
前置知识
Proxy ES6语法 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
Proxy 可以理解成, 在目标对象之前架设一层“ 拦截”, 外界对该对象的访问, 都必须先通过这层拦截, 因此提供了一种机制, 可以对外界的访问进行过滤和改写。
语法:const p = new Proxy(target, handler)
target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler 一个通常以函数作为属性的对象, 各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
handler.get() 方法用于拦截对象的读取属性操作。
handler.set() 方法是设置属性值操作的捕获器。
handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法这些方法与proxy handlers的方法相同。
Reflect.get(target, propertyKey[, receiver])
Reflect.deleteProperty(target, propertyKey)
Reflect.set(target, propertyKey, value[, receiver])
target: 需要取值的目标对象; key: 需要获取的值的键值;value::设置的值。
如果target对象中指定了getter, receiver则为getter调用时的this值
1. Proxy对数据拦截监听的基本使用
1、用Proxy将目标对象data进行包装拦截处理如下:
const proxyData = new Proxy(data, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
console.log('get', key) //监听
return result //返回结果
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
console.log('set', key, value) //set age 30
return result //是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key) //delete property name
return result //是否删除成功
},
})
2、定义data为对象,并对data对象进行操作时
const data = {
name: 'lili',
age: 20,
}
// 对象操作
proxyData.age // get操作 : get age
proxyData.age = 30 // set操作 : set age 30
proxyData.sex = "女" // set操作 : set sex 女
delete proxyData.name // 删除操作 : delete property name
不足:
在对象设置属性时,无法确定是新增属性还是原有属性;
3、定义data为数组,并对data数组进行操作
const data =['a','b','c']
proxyData.push('d')
// get push push()方法触发
// get length //获取数组长度
// set 3 d //设置值
// set length 4 设置数组长度
不足:
给数组添加元素时,没必监听 原型的属性,如push(),只需要监听本身(非原型)的属性,
set 3 d,set length 4 为重复处理同一个数据,set length 4多余
2. Proxy对数据拦截监听使用的优化
针对以上问题,对Proxy对数据拦截监听使用进行优化
1.在对象设置属性时,无法确定是新增属性还是原有属性
在set方法中判断
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key') //监听
} else {
console.log('新增的 key')
}
2、在监听属性时,只监听本身(非原型)的属性
在get方法中,判断如果是自身的属性,才进行监听
// Reflect.ownKeys()方法可以返回包含Symbol属性在内的自有属性。
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get',key)//监听
}
3、重复的数据不处理
在 set方法中,重复数据不处理
const oldVal=target[key]
if (value === oldVal) {
return true
}
以上问题,Proxy对数据拦截监听使用的优化后:
const proxyData = new Proxy(data, {
// target:目标对象、 key:被捕获的属性名、receiver:Proxy或者继承Proxy的对象
get(target, key, receiver) {
// 只监听 处理本身(非原型)的属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) //监听
}
const result = Reflect.get(target, key, receiver)
return result //返回结果
},
// value 新属性值。
set(target, key, value, receiver) {
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key') //监听
} else {
console.log('新增的 key')
}
// 重复的数据不处理
if (value === target[key]) {
return true
}
const result = Reflect.set(target, key, value, receiver)
console.log('set', key, value) //set age 30
return result //是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key) //delete property name
return result //是否删除成功
},
})
定义data为数组,并对data数组进行操作时
const data =['a','b','c']
proxyData.push('d')
// get length //获取数组长度
// set 3 d //设置值
实现了只保留了对自身属性的监听,重复数据没有重复设置
3. Proxy实现响应式
实现思路:
① 创建响应式方法reactive(data),该方法可以传入需要处理的数据对象data
函数内逻辑:
② 判断 data 是否为 对象或者数组,不是直接返回
③ 创建 Proxy 代理对象,Proxy对象中传入data
Proxy 代理对象中的方法配置:
④ 在get()方法对数据的进行监听:只监听 处理本身(非原型)的属性;在返回结果中采用递归调用reactive(),实现对数据的深度监听
⑤ 在 set() 方法中进行数据的新增和更新:判断是否是新增数据;重复的数据不处理;
⑥ 在 deleteProperty() 方法中对数据进行删除操作;
实例:
⑦ 定义数据data,传入响应式方法中,返回的值proxyData为实现响应式的可操作数据
// 创建响应式
function reactive(target = {}) {
if (typeof target != 'object' || target == null) {
// 不是对象或者数组,则返回
return target
}
// 代理配置 生成代理对象
return observed = new Proxy(target, {
// target:目标对象、 key:被捕获的属性名、receiver:Proxy或者继承Proxy的对象
get(target, key, receiver) {
// 只监听 处理本身(非原型)的属性 ,如push()
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) //监听
}
const result = Reflect.get(target, key, receiver)
// return result //返回结果
// 深度监听
// 性能如何提升的?
return reactive(result) //递归get处理 实现深度监听
},
// value 新属性值。
set(target, key, value, receiver) {
// 判断是否是新增属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key') //监听
} else {
console.log('新增的 key')
}
// 重复的数据不处理
const oldVal = target[key]
if (value === oldVal) {
return true
}
const result = Reflect.set(target, key, value, receiver)
console.log('set', key, value) //set age 30
return result //是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key) //delete property name
console.log('result', result) //result true
return result //是否删除成功
},
})
}
// 测试数据
const data = {
name: 'lili',
age: 20,
info: {
city: "beijing"
}
}
const proxyData = reactive(data)
vue3.0 与 vue2.* 实现响应式 比较
vue3.0 基于 Proxy 实现响应式
vue2.0 基于 Oject.defineProperty 实现响应式
相较于vue2,Proxy 实现响应式:
- 深度监听,性能更好
- 可监听新增删除的属性
- 可监听数组变化
总结:
- Proxy 能规避 Oject.defineProperty的问题
- Proxy无法兼容所有浏览器,无法 polyfill
Reflect的作用总结
1.和 Proxy 能力一一对应
2.规范化、标准化、函数式
const obj={a:100,b:200}
'a' in obj //true
Reflect.has(obj,'a')//true
delete obj.b //true
Reflect.deleteProperty(obj,'b') //true
- 3.替代掉Object上的工具函数
const obj={a:100,b:200}
Object.getOwnPropertyNames(obj) //["a", "b"]
Reflect.ownKeys(obj) // ["a", "b"]