一、 Javascript 代理 Proxy
Proxy的小结
- get handler
- set handler
- has handler
- deleteProperty handler
是 Proxy 最常见的几种 handler,我们可以在代理上原始的 target 之后,再封装自己的业务逻辑。
不被 IE 浏览器支持,vue3弃用 IE
二、Reflect-Proxy的好帮手
初识Reflect
Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it's not constructible.
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
主要特征:
- 不可构造,不能使用 new 进行调用
- 所有方法和 Proxy handlers 相同
- 所有的方法都是静态方法,类似于
Math - 很多方法和
Ojbect相同,但行为略微有所区别。譬如Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
静态方法列表:
Reflect对象一共有 13 个静态方法(匹配Proxy的13种拦截行为)。
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.ownKeys(target)
- ...
具体参见 MDN 文档-Reflect
简写 Reflect 的调用
在 Proxy handler 内部, 因为所有的参数都一致,所以我们可以用...arguments 简化调用。
...
const handler = {
get(target, prop, receiver) {
return Reflect.get(...arguments);
}
}
...
三、 Part 3 - Proxy + Reflect + activeEffect
组合 Proxy + Effect 存储
const targetMap = new WeakMap();
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(effect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
let dep = depsMap.get(key);
if (dep) {
dep.forEach(element => {
element();
});
}
}
function reactive(target) {
const handler = {
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver);
track(target, key);
return result;
},
set(target, key, value, receiver) {
let oldValue = target[key];
let result = Reflect.set(target, key, value, receiver);
if (result && oldValue != value) {
trigger(target, key);
}
return result;
}
}
return new Proxy(target, handler);
}
let param = reactive({ width: 5, height: 2 });
let size = 0;
let effect = () => {
size = param.width * param.height;
}
effect();
console.log(size); // => 10;
param.height = 3;
console.log(size); // => 15
param.width = 6;
console.log(size); // => 18
没有依赖却触发了 track 或 trigger问题以及 activeEffect 解决问题
const targetMap = new WeakMap();
let activeEffect = null // The active effect running
function track(target, key) {
if (!activeEffect) return; // 如果没有 activeEffect 就返回
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect); // 依赖添加 activeEffect
}
...
// trigger、 reactive 两个函数实现保持不变
function effect(eff) {
activeEffect = eff // 将 eff 函数设为 activeEffect
activeEffect() // 执行 activeEffect,因为它内部会获取各个 target 的 属性,所以会收集依赖,执行相应 key 的 track。
activeEffect = null // Unset it
}
let param = reactive({ width: 5, height: 2, radius: 10 });
let size = 0;
let newWidth = 0;
effect(() => {
size = param.width * param.height;
});
effect(() => {
newWidth = param.width * 3;
});
console.log(`size is ${size}, newWidth is ${newWidth}`); // size is 10, newWidth is 15
param.width = 10;
console.log(`size is ${size}, newWidth is ${newWidth}`); // size is 20, newWidth is 30
console.log(param.width, param.radius); // 不会触发 width \ radius 的 track
param.radius = 10; // 不会触发 trigger ,因为没有被依赖到
关键点:
function track(target, key) {
if (!activeEffect) return; // 如果没有 activeEffect 就返回
...
dep.add(activeEffect); // 依赖添加 activeEffect
}
function effect(eff) {
activeEffect = eff
activeEffect()
activeEffect = null
}
effect(() => {
size = param.width * param.height;
});