代理响应式

97 阅读2分钟

一、  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;
});