1. Proxy 与 Reflect 简介

代理(Proxy)是一种设计模式,允许为其他对象创建一个代表或占位符对象,以控制对它的访问。在JavaScript中,Proxy 对象是一个内置对象,用于创建一个对象的代理,从而可以拦截和自定义对象的基本操作,如属性查找、赋值、枚举、函数调用等。

反射(Reflect)是ES6中引入的一个内置对象,它提供了拦截JavaScript操作的方法,这些方法与Proxy对象的陷阱(trap)方法相对应。Reflect对象的方法可以用来实现默认行为,使得在Proxy中可以方便地定义自定义行为。

1.1 Proxy 代理

Proxy 对象主要用于以下目的:

  • 验证对象属性的访问权限。
  • 捕获和修改对象的赋值操作。
  • 拦截对象方法的调用。

创建一个Proxy对象需要两个参数:目标对象(target)和处理程序对象(handler)。处理程序对象可以定义多种拦截器方法,例如getsetapply等。

示例代码:

 1const target = {};
 2const handler = {
 3  get: function(target, name) {
 4    if (name === 'hello') {
 5      return 'world';
 6    }
 7    return Reflect.get(...arguments);
 8  }
 9};
10
11const proxy = new Proxy(target, handler);
12console.log(proxy.hello);

1.2 Reflect 反射

Reflect 对象提供了一个方法的集合,用于执行与Proxy中的陷阱相对应的操作。这些方法可以用来实现默认行为,或者在不使用Proxy的情况下,以一种与操作符相似的方式操作对象。

Reflect的一些常用方法包括:

  • Reflect.get(target, propertyKey[, receiver]):获取属性值。
  • Reflect.set(target, propertyKey, V[, receiver]):设置属性值。
  • Reflect.apply(target, thisArgument, argumentsList):调用函数。
  • Reflect.construct(target, argumentsList[, newTarget]):以给定的构造函数创建一个新对象。

示例代码:

 1const obj = { x: 1, y: 2 };
 2const result = Reflect.get(obj, 'x');
 3console.log(result); 
 4
 5Reflect.set(obj, 'z', 3);
 6console.log(obj.z);

ProxyReflect 的结合使用,为JavaScript提供了强大的代理和反射功能,使得开发者可以更加灵活地控制和操作对象。

2. Proxy 的作用与应用场景

2.1 代理的作用

Proxy 对象在JavaScript中扮演着一个非常重要的角色,它允许开发者定义对目标对象操作的自定义行为。以下是Proxy的一些关键作用:

  • 拦截操作Proxy 可以拦截目标对象的各种操作,包括属性访问、赋值、枚举、函数调用等。
  • 自定义行为:通过自定义拦截器函数,可以对目标对象的操作进行额外的处理,例如记录日志、验证权限、数据格式化等。
  • 保护对象Proxy 可以用来保护目标对象,防止外部代码直接访问或修改对象的内部状态。
  • 功能扩展:可以在不修改原有对象的基础上,通过代理扩展对象的功能。

2.2 应用场景

Proxy 在实际开发中有多种应用场景,以下是一些常见的例子:

  • 数据验证:在对象属性赋值时进行数据类型或范围的验证,确保数据的有效性。
  • 访问控制:控制对对象属性的访问权限,例如在Web应用中根据用户角色限制对某些属性的访问。
  • 缓存机制:为对象的属性访问实现缓存,提高性能,特别是在复杂的计算属性或远程数据访问时。
  • 日志记录:在访问或修改对象属性时自动记录日志,便于调试和追踪。
  • 代理模式:实现代理模式,例如远程代理、虚拟代理、保护代理等,以控制对复杂对象的访问。

示例应用

以下是Proxy在不同场景下的应用示例:

数据验证
 1const user = {};
 2const userProxy = new Proxy(user, {
 3  set: function(target, key, value) {
 4    if (key === 'age' && typeof value !== 'number') {
 5      throw new TypeError('Age must be a number');
 6    }
 7    target[key] = value;
 8    return true;
 9  }
10});
11
12try {
13  userProxy.age = 'thirty'; 
14} catch (e) {
15  console.error(e);
16}
缓存机制
 1const cache = {};
 2const cachedFunctionProxy = new Proxy(function() {}, {
 3  get: function(target, name) {
 4    const args = Array.from(arguments).slice(2);
 5    const key = JSON.stringify(args);
 6    if (!cache[key]) {
 7      cache[key] = Reflect.apply(target, this, args);
 8    }
 9    return cache[key];
10  }
11});
12
13const computeExpensiveValue = cachedFunctionProxy;
14console.log(computeExpensiveValue(1, 2, 3, 4)); 
15console.log(computeExpensiveValue(1, 2, 3, 4));

通过这些示例,我们可以看到Proxy在实际开发中的灵活性和强大功能,它为JavaScript对象的操作提供了无限的可能性。

3. Reflect 的作用与应用场景

3.1 Reflect 的作用

Reflect 对象在JavaScript中充当着一个实用工具的角色,它提供了一种方法来执行与Proxy中的陷阱相对应的操作。以下是Reflect的一些主要作用:

  • 默认操作Reflect提供了一组与Proxy中的陷阱方法相对应的静态方法,用于执行默认操作。
  • 一致性Reflect的方法与Proxy的陷阱方法一一对应,这使得在Proxy中定义自定义行为时,可以很容易地调用默认行为。
  • 布尔值返回:与Proxy的陷阱方法不同,Reflect的方法返回布尔值,表示操作是否成功,这为编程提供了更明确的反馈。
  • 属性操作Reflect提供了操作对象属性的方法,如Reflect.setReflect.get等,这些方法可以用来在不直接操作对象的情况下,获取或设置属性值。

3.2 应用场景

Reflect在JavaScript开发中也有多种应用场景,以下是一些实例:

  • 属性访问:在不直接操作对象的情况下,使用Reflect.getReflect.set来安全地访问和设置对象的属性。
  • 函数调用:使用Reflect.apply来调用函数,这在某些情况下比使用Function.prototype.apply更清晰。
  • 构造函数:使用Reflect.construct来创建对象,这为构造函数提供了一种新的使用方式。
  • 属性检查:使用Reflect.has来检查对象是否具有特定的属性,这比使用in操作符更直接。
  • 属性描述:使用Reflect.getOwnPropertyDescriptor来获取属性的描述,这对于属性的元编程非常有用。

示例应用

以下是Reflect在不同场景下的应用示例:

属性访问
 1const obj = { a: 1 };
 2const value = Reflect.get(obj, 'a');
 3console.log(value); 
 4
 5Reflect.set(obj, 'b', 2);
 6console.log(obj.b);
函数调用
 1function sum(a, b) {
 2  return a + b;
 3}
 4
 5const result = Reflect.apply(sum, null, [1, 2]);
 6console.log(result);
构造函数
 1function Person(name) {
 2  this.name = name;
 3}
 4
 5const person = Reflect.construct(Person, ['Alice']);
 6console.log(person.name);
属性检查
 1const obj = { x: 1 };
 2const hasY = Reflect.has(obj, 'y');
 3console.log(hasY);
属性描述
 1const obj = { writable: true };
 2const descriptor = Reflect.getOwnPropertyDescriptor(obj, 'writable');
 3console.log(descriptor.enumerable);

通过这些示例,我们可以看到Reflect在JavaScript中提供了一种与Proxy相对应的、更为直接和安全的方式来操作对象和函数。

4. Proxy 与 Reflect 的结合使用

4.1 拦截与默认行为的结合

Proxy对象中,通过拦截器方法可以定义自定义行为,而Reflect对象提供了执行默认操作的方法。结合使用ProxyReflect,可以在自定义操作和默认行为之间灵活切换。

示例:自定义属性访问

 1const target = { name: 'Alice' };
 2const handler = {
 3  get: function(target, name) {
 4    if (name === 'age') {
 5      return 25;
 6    }
 7    return Reflect.get(...arguments);
 8  }
 9};
10
11const proxy = new Proxy(target, handler);
12console.log(proxy.name); 
13console.log(proxy.age);

4.2 拦截器方法与Reflect方法的对应

Reflect对象的方法与Proxy的拦截器方法一一对应,这使得在Proxy中调用默认行为变得简单。

示例:拦截器方法与Reflect方法对应

 1const target = {};
 2const handler = {
 3  set: function(target, property, value) {
 4    console.log(`Setting ${property} to ${value}`);
 5    return Reflect.set(target, property, value);
 6  }
 7};
 8
 9const proxy = new Proxy(target, handler);
10proxy.age = 30;

4.3 代理的撤销

Proxy对象是可撤销的,这意味着可以创建一个代理,然后在某个时刻撤销它,使得代理不再有效。

示例:代理的撤销

 1const target = {};
 2const handler = {
 3  get: function(target, name) {
 4    return name in target ? target[name] : 42;
 5  }
 6};
 7
 8const {proxy, revoke} = Proxy.revocable(target, handler);
 9console.log(proxy.a); 
10proxy.a = 5;
11console.log(proxy.a); 
12
13revoke();
14
15console.log(proxy.a);

4.4 代理和反射在异步操作中的应用

ProxyReflect不仅可以用于同步操作,还可以与异步操作结合使用,例如拦截异步函数调用或处理异步属性访问。

示例:异步函数调用拦截

 1const asyncFunction = function() {
 2  return Promise.resolve("Result of async function");
 3};
 4
 5const asyncFunctionProxy = new Proxy(asyncFunction, {
 6  apply: function(target, thisArg, argumentsList) {
 7    console.log("Async function is about to be called");
 8    return Reflect.apply(...arguments).then(result => {
 9      console.log("Async function has completed");
10      return result;
11    });
12  }
13});
14
15asyncFunctionProxy().then(result => {
16  console.log(result); 
17});

通过结合使用ProxyReflect,JavaScript开发者可以创建强大且灵活的代理,以控制和扩展对象的行为,同时保持对默认操作的访问。这种结合使用为现代JavaScript应用程序提供了广泛的应用潜力。

个人笔记记录 2021 ~ 2025