Proxy and Reflect

Proxy and Reflect

Our current code

let product = { price: 5, quantity: 2 };
let total = 0;
let effect = () => (total = product.price * product.quantity);

track(product, "quantity");
effect();
console.log(total);

product.quantity = 3;
trigger(product, "quantity");
console.log(total);

目前仍然在调用 track 和 trigger 来记录保存我们的 effect,并且调用它。我们希望它会自动调用 track 和 trigger。

我们在运行 effect 的时候,如果访问了 product 的 properties,或者说是用了GET,那正是我们想调用track去保存 effect 的时候。

当 product 的 properties 改变,或者说是用了SET,则调用trigger

// When running an effect if product properties ara accessed (GET)
// then call
track(product, property); // ---> to save this effect
// If product properties are changed (SET)
// then call
trigger(product, property); // ---> to run saved effects

问题来到了,如何拦截 SET 和 GET,Vue2 采用 ES5 Object.defineProperty(), Vue3 中使用 ES6 的 Reflect 和 Proxy。

How to intercept GET&SET?

Understanding ES6 Reflect

let product = { price: 5, quantity: 2 };

这是一个对象,有三个方法访问它。

// Three ways to print out a property on an object
console.log("quantity is " + product.quantity); // Dot notation
console.log("quantity is " + product["quantity"]); // Bracket notation
console.log("quantity is " + Reflect.get(product, "quantity"));
// All three are valid, but Reflect has a super power you'll see in a minute

三种都可以,但是 Reflect 拥有一种特殊能力,在这之前先理解Proxy

Understanding ES6 Proxy

Proxy 是另一个对象的占位符,默认情况下对该对象进行委托。(a placeholder for another object which by default delegates to that object)

let product = { price: 5, quantity: 2 };

let proxiedProduct = new Proxy(product, {});

console.log(proxiedProduct.quantity); // proxiedProduct -> product -> proxiedProduct -> console.log
// 2

现在声明一个 proxy,当我们调用 proxiedProduct.quantity 时,他会先调用 Proxy(proxiedProduct),再调用 product,再返回到 Proxy(proxiedProduct),再返回这个 product 到 console 然后输出。

Using a Proxy get trap

声明 Proxy 的时候,第二个参数称为handler,再 handler 里可以传递一个trap,它可以让我们拦截一些基本操作,例如属性查找、枚举、函数调用(Property lookup、Enumeration、Function invocation)。

proxiedProduct = new Proxy(product, {
  get() {
    console.log("Get was called");
    return "Not the value";
  },
});

console.log(proxiedProduct.quantity);

现在调用 proxiedProduct.quantity,他会先打印,Get was called,然后返回 Not the value,再打印。现在修改一下。

Intercepting GET using ES Proxy
proxiedProduct = new Proxy(product, {
  get(target, key) {
    console.log("Get was called with key = " + key);
    return target[key];
  },
});

console.log(proxiedProduct.quantity);
// In this case, the target is our product, which we send as the first parameters.
// And the key is quantity

在这里,当我们调用 proxiedProduct.quantity 时,传递到 proxiedProduct 的 get,第一个参数 target 即为 product,我们把 product 在声明 Proxy 的时候作为第一个参数传入,第二个参数则为 quantity。

输出 Get was called with key = quantity 和 2。这里 Reflect 就来了。

Using Proxy with Reflect
proxiedProduct = new Proxy(product, {
  get(target, key, receiver) {
    console.log("Get was called with key = " + key);
    return Reflect.get(target, key, receiver);
  },
});

这里我们给多一个参数 receiver 给 get,同时把它传递给 Reflect。它保证了当我们的对象有继承自其他对象的值或函数时,this 可以正确的执行使用(的对象)。(Ensures the proper value of this is used when our object has inherited values or functions from another object)打印的结果和以前相同。

Now with a Setter Method

现在拦截 set。

proxiedProduct = new Proxy(product, {
  get(target, key, receiver) {
    console.log("Get was called with key = " + key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log("Set was called with key = " + key + " and value = " + value);
    return Reflect.set(target, key, value, receiver);
  },
});
proxiedProduct.quantity = 4;
console.log(proxiedProduct.quantity);
Set was called with key = quantity and value = 4
Get was called with key = quantity
4
Encapsulated like Vue3

现在封装这段 get 和 set 使它变得更新 Vue 3 的源代码

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      console.log("Get was called with key = " + key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      console.log("Set was called with key = " + key + " and value = " + value);
      return Reflect.set(target, key, value, receiver);
    },
  };
  return new Proxy(target, handler);
}

product = reactive({ price: 5, quantity: 2 });
proxiedProduct.quantity = 4;
console.log(proxiedProduct.quantity);

取名 reactive,就像 Composition API 一样。

Back to our current code

记得开头,为了调用 track,我们需要一些方法监听 GET 请求,调用 trigger,需要监听 SET 请求。现在来看我们应该分把他们放到 reactive 函数的哪个位置。

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 (!oldValue != result) trigger(target, key);
      return result;
    },
  };
  return new Proxy(target, handler);
}

let product = reactive({ price: 5, quantity: 2 });
Removing track and trigger
let product = reactive({ price: 5, quantity: 2 });
let total = 0;
let effect = () => (total = product.price * product.quantity);

track(product, "quantity");
effect();
console.log(total);

product.quantity = 3;
trigger(product, "quantity");
console.log(total);

我们不再需要调用 track 和 trigger 了。删掉

let product = reactive({ price: 5, quantity: 2 });
let total = 0;
let effect = () => (total = product.price * product.quantity);

effect();
console.log(total);

product.quantity = 3;
console.log(total);
Now with Reactivity

现在从头到尾看一次这段代码执行过程。

首先声明变量。

let product = reactive({ price: 5, quantity: 2 });
let total = 0;
let effect = () => (total = product.price * product.quantity);

effect 里,当我们试图得到价格(product.price)时候,运行

track(product, "price");

而此时,在 targetMap 里,它会为 product 创建一个新的映射。其值是一个新的 depsMap,这将映射价格属性到一个新的 dep,而这个 dep 就是 effects 集(Set),把我们所有 effects 添加到集(Set)中。

试图得到 product.quantity 时候,运行

track(product, "quantity");

这时他会访问 targetMap 里的 product,找到其 depsMap 建一个新的值 quantity,并映射到一个新的 dep 中。

此时继续往下运行。

effect();
console.log(total);

输出 total(10)到控制台。

此时

product.quantity = 3;

触发

trigger(product, "quantity");

然后运行储存了的 effect。更新了 total

此时 total 成了 15

// All code
const targetMap = new WeakMap(); // For storing the dependencies for each reactive object

function track(target, key) {
  // Add the code
  let depsMap = targetMap.get(target); // Get the current depsMap for this target(reactive object)
  if (!depsMap) targetMap.set(target, (depsMap = new Map())); // If it doesn't exist, create it(product)

  let dep = depsMap.get(key); // Get the dependency object for this property (quantity)
  if (!dep) depsMap.set(key, (dep = new Set())); // If it doesn't exist, create it
  dep.add(effect); // Add the effect to the dependency
}

function trigger(target, key) {
  // Run again all the code.
  const depsMap = targetMap.get(target); // Does the object have any properties that have dependencies?
  if (!depsMap) return; // If no, return from the function immediately
  let dep = depsMap.get(key); // Otherwise, check if this property has a dependency.
  if (dep) dep.forEach((effect) => effect()); // Run those
}
//------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------
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 (!oldValue != result) trigger(target, key);
      return result;
    },
  };
  return new Proxy(target, handler);
}

let product = reactive({ price: 5, quantity: 2 });
let total = 0;
let effect = () => (total = product.price * product.quantity);

effect();
console.log(total);

product.quantity = 3;

console.log(total);

在这里插入图片描述

2 - Proxy and Reflect_哔哩哔哩_bilibili

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值