JavaScript 对象管家 Proxy

时间:2022-03-05 00:48:36

JavaScript 对象管家 Proxy

JavaScript 在 ES6 中,引入了一个新的对象类型 ​​Proxy​​​,它可以用来代理另一个对象,并可以在代理过程中拦截、覆盖和定制对象的操作。​​Proxy​​ 对象封装另一个对象并充当中间人,其提供了一个捕捉器函数,可以在代理对象上拦截所有的操作,包括访问属性、赋值属性、函数调用等等。通过拦截这些操作,可以对代理对象进行定制和控制。


JavaScript 对象管家 Proxy

在开始介绍 ​​Proxy​​ 对象前先了解 3 个术语:

  1. ​target 目标对象​​:要代理的对象或函数。
  2. ​handler 处理程序​​:对代理的对象或函数执行某些操作的函数。
  3. ​traps 捕捉器​​:这些是一些用于处理目标的函数。单击此处阅读有关陷阱的更多信息。

语法

​Proxy​​ 对象的基本语法如下:

new Proxy(target, handler);

其中,​​target​​​ 是被代理的目标对象,​​handler​​ 是一个对象,它包含了一些捕捉器函数,用来拦截代理对象的操作。

下面是一些常见的拦截操作和对应的捕捉器函数:

对象方法
  1. ​getPrototypeOf()​​​:​​Object.getPrototypeOf​​ 方法的捕捉器。
  2. ​setPrototypeOf()​​​:​​Object.setPrototypeOf​​ 方法的捕捉器。
  3. ​isExtensible()​​​:​​Object.isExtensible​​ 方法的捕捉器。
  4. ​preventExtensions()​​​:​​Object.preventExtensions​​ 方法的捕捉器。
  5. ​getOwnPropertyDescriptor()​​​:​​Object.getOwnPropertyDescriptor​​ 方法的捕捉器。
  6. ​handler.defineProperty()​​​:​​Object.defineProperty​​ 方法的捕捉器。
属性获取器/设置器
  1. ​get(target, propKey, receiver)​​:拦截对象的读取属性操作,返回属性值。
  2. ​set(target, propKey, value, receiver)​​:拦截对象的设置属性操作,返回一个布尔值表示是否设置成功。
  3. ​has(target, propKey)​​​:拦截对象的 ​​in​​ 操作符,返回一个布尔值表示对象是否包含该属性。
  4. ​deleteProperty(target, propKey)​​​:拦截对象的 ​​delete​​ 操作符,返回一个布尔值表示是否删除成功。
  5. ​ownKeys()​​​:​​Object.getOwnPropertyNames​​​ 方法和 ​​Object.getOwnPropertySymbols​​ 方法的捕捉器
函数方法

如果目标对象是一个函数,可以使用下面 2 个捕捉器。

  1. ​apply(target, thisArg, args)​​:拦截函数的调用操作,返回调用结果。
  2. ​construct(target, args, newTarget)​​​:拦截 ​​new​​ 操作符,返回一个对象。

​Proxy​​​ 在目标对象周围创建一个不可检测的屏障,将所有操作重定向到处理程序对象。如果发送一个空的 ​​handler​​ ,代理只是原始对象的一个空包装器。

const author = {
name: "Quintion",
age: 36,
};

const proxyAuthor = new Proxy(author, {});

console.log(author.name); // Quintion
console.log(proxyAuthor.name); // Quintion

为了赋予代理意义,需要向处理程序添加一些操作方法。

捕捉器

每当与一个对象交互时,都在调用一个内部方法。代理允许使用捕捉器拦截给定内部方法的执行。

因此,当运行 ​​author.name​​​ 时,告诉 JavaScript 引擎调用内部 ​​[[GET]]​​​ 方法来检索 ​​name​​​ 属性。当运行 ​​proxyAuthor.name​​​ 时,​​get​​​ 捕捉器会调用处理程序中定义的 ​​get()​​ 函数来执行,然后再将调用发送到原始对象。

JavaScript 对象管家 Proxy

get

​get()​​ 方法有两个必需的参数:

  • ​target​​ — 传递给代理的对象。
  • ​property​​ — 访问的属性的名称。

要自定义代理,在处理程序对象上定义函数。下面定义了 ​​get​​ 方法来记录访问:

const handler = {
get(target, property) {
console.log(`捕捉器 GET:${property}`);
return target[property];
},
};

为了让调用通过,捕捉器 ​​get​​​ 返回 ​​target[property]​​。使用方式如下:

const author = {
name: "Quintion",
age: 36,
};

const handler = {
get(target, property) {
console.log(`捕捉器 GET[${property}]`);
return target[property];
},
};

const proxyAuthor = new Proxy(author, handler);

console.log(proxyAuthor.name);

执行后,将打印以下内容:

捕捉器 GET[name]
Quintion
set

​set​​​ 捕捉器用于给目标对象进行赋值操作,返回值是一个布尔值。​​set​​ 捕捉器需要的参数如下:

  • ​target​​ — 传递给代理的对象。
  • ​property​​ — 将被设置的属性名或 Symbol。
  • ​value​​ — 新的属性值
  • ​receiver​​ — 最初被调用的对象。

下面通过 ​​set​​ 捕捉器验证年龄值的输入:

const handler = {
set(target, property, value) {
if (property === "age" && typeof value !== "number") {
throw new TypeError("年龄必须是一个数字");
}

target[property] = value;
return true;
},
};

下面尝试将错误的类型值赋值给 ​​age​​ ,则会抛出错误:

const proxyAuthor = new Proxy(author, handler);

proxyAuthor.age = "young";
// 执行后抛出异常:throw new TypeError("年龄必须是一个数字");

​set()​​​ 方法应该返回一个布尔值 ​​true​​ 用来表示赋值成功。 在严格模式下运行,并且返回一个假值或什么都不返回,则会抛出错误。

除了拦截对属性的读取和修改,​​Proxy​​ 总共可以拦截 13 种操作。

应用场景

通过 ​​Proxy​​ 对象的特征,可以将其使用在下面这些场合:

验证和过滤

代理​​Proxy​​​ 用于拦截和验证对对象属性的访问。如,可以创建一个代理来检查用户输入的数据是否符合预期的格式,并拒绝不正确的数据。就如下面 ​​age​​ 属性赋值判断

缓存

代理​​Proxy​​ 用于缓存对象的操作结果,以避免重复计算。如,可以创建一个代理来拦截对象的某些方法,并将结果存储在缓存中,以便将来使用。

下面是一个基于 Proxy 的缓存库的示例:

class Cache {
constructor() {
this.cache = new Map();
this.proxy = new Proxy(this, {
get(target, property) {
if (property === "get") {
return (key) => {
return target.cache.get(key);
};
}
if (property === "set") {
return (key, value) => {
target.cache.set(key, value);
};
}
if (property === "has") {
return (key) => {
return target.cache.has(key);
};
}
if (property === "delete") {
return (key) => {
return target.cache.delete(key);
};
}
},
});
}
}

在上面的代码中,定义了一个 ​​Cache​​​ 类,该类中包含一个内部的 ​​Map​​​ 对象用于存储缓存数据,并且定义了一个 ​​proxy​​ 对象作为该类的代理。

在 ​​proxy​​​ 对象的 ​​get​​​ 方法中,根据传入的属性名返回相应的方法。如果属性名为 ​​get​​​,则返回一个可以获取缓存值的方法;如果属性名为 ​​set​​​,则返回一个可以设置缓存值的方法;如果属性名为 ​​has​​​,则返回一个可以判断是否存在缓存值的方法;如果属性名为 ​​delete​​,则返回一个可以删除缓存值的方法。

下面是一个使用该缓存库的示例:

const cacheHelper = new Cache();

cacheHelper.set("foo", "bar");
console.log(cacheHelper.get("foo")); // "bar"
console.log(cacheHelper.has("foo")); // true

cacheHelper.delete("foo");
console.log(cacheHelper.get("foo")); // undefined
console.log(cacheHelper.has("foo")); // false

在上面的代码中,创建了一个 ​​Cache​​​ 对象,并调用其 ​​set​​​ 方法设置缓存值,然后调用其 ​​get​​​ 方法获取缓存值,并调用其 has 方法判断缓存值是否存在,最后调用其 ​​delete​​ 方法删除缓存值。

监听属性变化

代理​​Proxy​​用于监视对象属性的变化,并在属性发生变化时触发其他操作。如,创建一个代理来监视对象属性的变化,并在属性发生变化时更新页面上的元素。

防止误操作

代理​​Proxy​​用于防止误操作,如,创建一个代理来拦截对象的某些方法,并在方法调用时检查一些条件,以确保方法只在正确的上下文中调用。

虚拟化

代理​​Proxy​​可以用于创建虚拟化对象。如,创建一个代理对象,用于代替某个对象的真实实现,并且在实际对象执行之前,对其进行修改或拦截。

总结

上面介绍了如何使用代理​​Proxy​​对象来监视对象,通过使用处理程序对象中的捕捉器方法向它们添加自定义行为,提供更高级的对象操作和控制功能,从而增强代码的可读性和可维护性。