我正在参加「掘金·启航计划」
vue3响应式原理之proxy
proxy
proxy是ES6提供的一种机制,用于修改目标对象的默认行为。通俗点理解,可当成拦截器,将对目标对象的某些操作进行拦截处理后再返回。
使用场景
vue项目中常用proxy来设置代理http、ws请求等vue3响应式数据的创建通过proxy实现
使用方法
创建proxy构造函数 new Proxy(target, handler)
target: 所要代理的目标对象handler: 配置对象,传入函数,即对目标对象拦截的一些操作,操作包括以下几种
handler可拦截的操作
-
get: 3个参数,目标对象、属性名、proxy实例本身 -
set: 4个参数,目标对象、属性名、属性值、proxy实例本身(可选),应返回true,否则在严格模式没有返回true则报错 -
has: 用于拦截hasProperty(无法拦截hasOwnProperty)操作,判断对象是否有某属性;2个参数,目标对象、查询的属性名。对in运算符生效,对for...in不生效; -
deleteProperty: 用于拦截delete操作,若返回false或者抛异常,则无法删除 -
ownKeys: 拦截对象自身属性的读写操作,传入一个参数target,作用于以下方法Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()for...in
-
getOwnPropertyDescriptor: 返回属性的描述对象或undefined,2个参数,target和key -
defineProperty: 拦截Object.defineProperty()操作,3个参数,目标函数、定义的属性名、描述;返回Boolean,为false表示操作失败,但本身不阻止添加新属性 -
getPrototypeOf: 用于拦截对象原型,1个传参target,作用于以下操作Object.prototype.__proto__Object.prototype.isPrototypeOf()Object.getPrototypeOf()Reflect.getPrototypeOf()instanceof
-
isExtensible: 用于拦截Object.isExtensible()操作,用于定义对象是否可扩展。该方法只能返回Boolean,否则自动转为Boolean -
preventExtensions: 拦截Object.preventExtensions(),必须返回Boolean,否则自动转为Boolean;传入一个参数target -
setPrototypeOf: 用于拦截Object.setPrototypeOf()(设置target的原型对象),返回值只能是Boolean -
apply: 用于拦截函数的调用、call和apply操作。3个参数,目标对象、目标对象的上下文对象(this)、目标对象的参数数组(args)
var handler = {
apply(target, ctx, args) {
return Reflect.apply(...arguments)
}
}
construct: 用于拦截new命令,3个参数,目标对象、构造函数的参数数组args、创造实例对象时,new命令作用的构造函数;必须返回一个对象,否则报错
实例
该实例是vue3源码-mini版中拿到的代码
先定义 get 和 set 方法
const get = createGetter();
const set = createSetter();
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
const isExistInReactiveMap = () =>
key === ReactiveFlags.RAW && receiver === reactiveMap.get(target);
const isExistInReadonlyMap = () =>
key === ReactiveFlags.RAW && receiver === readonlyMap.get(target);
const isExistInShallowReadonlyMap = () =>
key === ReactiveFlags.RAW && receiver === shallowReadonlyMap.get(target);
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly;
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly;
} else if (
isExistInReactiveMap() ||
isExistInReadonlyMap() ||
isExistInShallowReadonlyMap()
) {
return target;
}
const res = Reflect.get(target, key, receiver);
// 问题:为什么是 readonly 的时候不做依赖收集呢
// readonly 的话,是不可以被 set 的, 那不可以被 set 就意味着不会触发 trigger
// 所有就没有收集依赖的必要了
if (!isReadonly) {
// 在触发 get 的时候进行依赖收集
track(target, "get", key);
}
if (shallow) {
return res;
}
if (isObject(res)) {
// 把内部所有的是 object 的值都用 reactive 包裹,变成响应式对象
// 如果说这个 res 值是一个对象的话,那么我们需要把获取到的 res 也转换成 reactive
// res 等于 target[key]
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
function createSetter() {
return function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 在触发 set 的时候进行触发依赖
trigger(target, "set", key);
return result;
};
}
通过new Proxy创建构造函数,通过get和set代理目标对象的读写操作
const mutableHandlers = {
get,
set,
};
// 代理目标对象的读写方法
const proxy = new Proxy(target, baseHandlers);