Vue3对比Vue2的变化
- 在Vue2的时候使用defineProperty来进行数据的劫持, 需要对属性进行重写添加
getter及setter性能差。 - 当新增属性和删除属性时无法监控变化。需要通过
$set、$delete实现 - 数组不采用defineProperty来进行劫持 (浪费性能,对所有索引进行劫持会造成性能浪费)需要对数组单独进行处理
Vue3中使用Proxy来实现响应式数据变化。从而解决了上述问题。
Proxy
我这边创建了一个仓库, 里面开发环境已经搭建好了, 直接拿来用就可以了, pnpm可以看这里
npm install -g pnpm // 全局安装一下pnpm
pnpm install -w
在packages/reactivity/src目录下创建一个 reactive.ts 先简单处理下对象的代理
import { isObject } from "@vue/shared";
// 1 将数据转化为响应式对象,并且只能做对象的代理
export function reactive(target) {
// 1.1 如果不是对象直接就return掉
if (!isObject(target)) {
return;
}
// 1.2 利用proxy做一个代理,并没有重新代理属性,而是访问代理对象时会走get,赋值时走set
const proxy = new Proxy(target, {
get(target, p, receiver) {
console.log(p)
return target[p];
},
set(target, p, value, receiver) {
target[p] = value;
return true;
},
});
return proxy;
}
这样就完成了一个对象最基本的代理功能, 下面在packages/reactivity/src目录下创建一个index.ts 导出reactive
export { reactive } from "./reactive";
运行 pnpm dev, 可以看到packages/reactivity/dist里面有两个文件, reactivity.global.js 和reactivity.global.js.map, 我们在dist 文件夹里面再创建一个 index.html 文件, 引入打包后的文件, 查看是否有reactive函数存在
<script src="./reactivity.global.js"></script>
<script>
const { reactive } = VueReactivity;
let wbw = {
name: "wbw",
age: 7,
address: { province: "gd", city: "gz" },
};
let wbwProxy = reactive(wbw);
console.log("wbwProxy", wbwProxy);
wbwProxy.name
wbwProxy.age
</script>
运行正常, 访问name和age都可以正常打印处理, 但是还有一种情况, 利用存取描述符get, set进行读取和设置Object.defineProperty() 可以了解一下
let wbw = {
name: "wbw",
age: 7,
address: { province: "gd", city: "gz" },
get myName() {
return this.name;
},
};
let wbwProxy = reactive(wbw);
// 这时候访问myName的时我们预期是希望打印出myName和name的,
// 但是上面的只会打印一次myName,而this.name的时候this指向的是wbw本身而不是代理对象,
// 所以没有走get,满足不了我们的预期
wbwProxy.myName
这个时候就需要用到 Reflect了, 不熟悉的朋友可以先了解一下, 下面将代码进行一个改造
const proxy = new Proxy(target, {
get(target, p, receiver) {
console.log(p);
return Reflect.get(target, p, receiver);
},
set(target, p, value, receiver) {
return Reflect.set(target, p, value, receiver);
},
});
这样就可以满足我们的预期了
缓存处理
接下来看下面的代码
const data1 = reactive(wbw);
const data2 = reactive(wbw);
console.log(data1 === data2); // false
代理相同的对象, 结果每次都是不同的实例产生, 我们希望代理相同对象的时候能返回的是同一个实例对象, 接下来看怎么处理一下, 这里用到了 WeakMap
import { isObject } from "@vue/shared";
// 2 利用WeakMap做缓存处理
const reactiveMap = new WeakMap()
// 1 将数据转化为响应式对象,并且只能做对象的代理
export function reactive(target) {
// 1.1 如果不是对象直接就return掉
if (!isObject(target)) {
return;
}
// 2.2 访问的时候先看下reactiveMap是否已经存在代理, 如果是就直接返回
let existingProxy = reactiveMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 1.2 利用proxy做一个代理,并没有重新代理属性,而是访问代理对象时会走get,赋值时走set
const proxy = new Proxy(target, {
get(target, p, receiver) {
return Reflect.get(target, p, receiver);
},
set(target, p, value, receiver) {
return Reflect.set(target, p, value, receiver);
},
});
// 2.1 每次都将target对象作为key将代理对象缓存起来
reactiveMap.set(target, proxy);
return proxy;
}
这样再看打印结果就是true了, 还有一种情况, 如果代理对象已经代理过了, 那么就直接返回
const data1 = reactive(wbw);
const data2 = reactive(data1);
console.log(data1 === data2); // false
import { isObject } from "@vue/shared";
// 3 给代理对象增加一个特殊的属性
const enum ReactiveFlags {
IS_REACTIVE = "__v_isReactive",
}
// 2 利用WeakMap做一个缓存处理
const reactiveMap = new WeakMap();
// 1 将数据转化为响应式对象, 并且只能做对象的代理
export function reactive(target) {
// 1.1 如果不是对象直接就return掉
if (!isObject(target)) {
return;
}
// 3.1 如果是普通对象, 必然没有get, 这里排除故意添加重名属性的
if (target[ReactiveFlags.IS_REACTIVE]) {
return target;
}
// 2.2 访问的时候先看下reactiveMap是否已经存在代理, 如果是就直接返回
let existingProxy = reactiveMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 1.2 利用proxy做一个代理,并没有重新代理属性,而是访问代理对象时会走get,赋值时走set
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 3.2 如果已经是代理对象了, 那么访问该key的时候就会进去这里
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
});
// 2.1 每次都将target对象为key将代理对象缓存起来
reactiveMap.set(target, proxy);
return proxy;
}
代码抽离
这个时候来做一个抽离, 把 ReactiveFlags 和 proxy handle抽到 packages/reactivity/src/baseHandle.ts 文件中
// reactive.ts
import { isObject } from "@vue/shared";
import { ReactiveFlags, mutabelHandles } from "./baseHandle"; // 引入
const proxy = new Proxy(target, mutabelHandles); // 修改这一行
// baseHandle.ts
export const enum ReactiveFlags {
IS_REACTIVE = "__v_isReactive",
}
export const mutabelHandles = {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
};
深度代理
import { isObject } from "@vue/shared";
import { reactive } from "./reactive";
export const mutabelHandles = {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
// 4 当取值时返回的值是对象,则返回这个对象的代理对象,从而实现深度代理
const res = Reflect.get(target, key, receiver);
if (isObject(res)) {
return reactive(res);
}
return res;
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
};
后续有时间会继续更新这个系列下去的......