# 前言
vue2的响应式是通过Object.defineProperty方法,劫持对象的getter和setter,在getter中收集依赖,在setter中触发依赖,但是这种方式有一些缺点
- 由于是遍历递归监听属性,当属性过多或嵌套层级过深时会影响性能
- 无法监听对象新增的属性和删除属性,只能监听对象本身存在属性,所以设计了$set 和 $delete
- 如果监听数组的话,无法监听数组的增减,只能监听通过下标可以访问到的数组中已有的属性,由于使用Object.defineProperty 遍历监听数组原有元素过于消耗,vue放弃使用 Object.defineProperty 监听数组,而采用重写数组原型的方式来监听对数组数据的操作,并用$set 和 splice方法来更新数组,$set和splice会调用重写后的数组方法
# Vue3响应式的实现
# Proxy对象
针对Object.defineProperty的弊病,在ES6中引入了一个新的对象-Proxy(代理)
proxy对象
用于创建一个对象的代理,主要用于改变对象的某些默认行为,Proxy可以理解成,在目标对象之前假设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外面的访问进行过滤和改写。基本语法如下
/**
* target: 目标对象
* handler: 配置对象,用来定义拦截的行为
* proxy:Proxy构造器实例
*/
var proxy = new Proxy(target, handler)
拦截get,取值操作
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35
}
})
proxy.time // 35
proxy.name // 35
proxy.title // 35
可以拦截的操作有
| 函数 | 操作 |
|---|---|
| get | 读取一个值 |
| set | 设置一个值 |
| has | in操作符 |
| deleteProperty | delete |
| getPrototypeOf | Object.getPrototypeOf() |
| setPrototypeOf | Object.setPrototypeOf() |
# Vue3的reactive 和 ref
Vue3的reactive和ref正式借助了Proxy来实现
# reactive
作用:创建原始对象的响应时副本,即将「引用类型」数据转换为「响应式」数据
参数: reactive数据必须是对象或数组
reactive函数实现
// 判断是否是对象
const isObject = val => val !== null && typeof val === 'object';
// 判断key是否存在
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
export function reactive(target) {
// 首先判断是否为对象
if(!isObject(target)) return target;
const handler = {
get(target, key, receiver) {
console.log(`获取对象属性${key}值`);
// 收集依赖
const result = Reflect.get(target, key, receiver);
// 深度监听(惰性)
if(isObject(result)) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
console.log(`设置对象属性${key}值为${value}`);
// 首先获取旧值
const oldValue = Reflect.get(target, key, reactive);
let result = Reflect.set(target, key, value, receiver);
if(result && oldValue !== value) {
// 更新操作...
}
return result;
},
deleteProperty(target, key) {
console.log(`删除对象属性${key}值`);
// 先判断是否有key
const hasKey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if(hadKey && result) {
// 更新操作
}
return result;
}
// 其他方法
// ....
}
return new Proxy(target, handler);
}
const obj = {a: {b: {c: 6}}};
const proxy = reactive(obj);
proxy.a.b.c = 10;
// 获取对象属性a值
// 获取对象属性b值
// 设置对象属性c值 77
至此,引用类型的对象我们已经把它转换为响应式对象了,Proxy对象只能代理引用类型的对象,对于基本数据类型如何实现响应式呢。
Vue的解决方法是把基本数据类型变成一个对象:这个对象只有一个value属性,value属性的值就等于这个基本数据类型的值。然后就可以用reative方法将这个对象编程响应式的proxy对象
// 实际上就是
ref{0} --> reactive({value: 0})
但是这是 Vue3 最初的设计,现在不太一样了,我们接着来看。
# ref
# 总结
- reactive将引用类型值变成响应式,使用proxy实现
- ref可将基本类型和引用类型都变成响应式,通过监听类的valeu属性的get和set实现,但是当传入的值为引用类型时实际上内部还是使用reactive方法进行处理
- ref经修改实现方式后性能更高,推荐使用ref一把梭