Javascript中的代理和反射

本文介绍了JavaScript中的代理(Proxy)和反射(Reflect)特性,详细阐述了代理的概念、处理程序对象中的捕获器、可撤销的代理,以及反射的API和应用场景。通过代理,可以实现对对象操作的拦截和自定义行为,而反射则提供了与代理对应的API,用于细粒度的对象控制。这两个特性为开发者提供了更强大的控制力和丰富的编码模式。
摘要由CSDN通过智能技术生成

写在前面


坚持,写博客,记笔记 😀, 加油!

ECMAScript 6 新增的代理与反射为开发者提供了拦截并向基本操作(如属性查找、赋值、枚举、函数调用等)嵌入额外行为的能力

也就是,可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标来使用

代理(Proxy: ['prɑksi]


概念

我个人的理解就是给目标对象添加了一层“拦截”,不管你是要对目标对象进行访问还是执行什么操作,都必须要经过这层“拦截”,而这层“拦截”想做什么(也就是上面所说的嵌入额外行为)则由代理来决定

使用 Proxy() 构造函数创建一个代理,这个构造函数接收两个参数:

  • 目标处理对象 target:可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
  • 处理程序对象 handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理的行为。

最简单的代理就是空代理,什么也不做仅仅是作为一个抽象的目标对象,这样在代理对象上执行的所有操作都会没有任何加工的直接到目标对象。

const target = { name: "ienyh" };
const proxy = new Proxy(target, {});

proxy.name = "ienyh_2";
console.log(proxy.name); // ienyh_2

处理程序对象 handler 中的捕获器

当我们想要在这层“拦截”中想做些什么也就是嵌入额外行为的时候,我们就需要在处理程序对象 handler 中定义要代理的行为了,也就是定义捕获器

捕获器就是在处理程序对象中定义的“基本操作的拦截器”

捕获器一共有 13 种分别是:get()set()has()defineProperty()getOwnPropertyDescriptor()deleteProperty()ownKeys()getPrototypeOf()setPrototypeOf()isExtension()preventExtensions()apply()construct()

这里暂时先只介绍get()set()这两种

1. get()

当代理对象执行get()操作(指 proxy[property]proxy.property 等读取操作)时就会触发get()捕获器,当然这个get()操作并不是 ECMAScript 对象可以直接调用的方法,而是由于 proxy[property]proxy.property 等取值操作触发的。

get 方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象 target、**属性名 key**和 代理对象 proxy (可选)。

注意:是在代理对象上执行上面的操作才会触发捕获器的行为,在原目标对象上执行这些操作依然是原始行为。

const student = { name: "ienyh", age: 21, gender: "male" };
const proxy = new Proxy(student, {
  get (target, key) {
    if (key === "age") {
      return target[key] + 1;
    }
    return target[key];
  },
});

console.log(proxy.name); // ienyh_2
console.log(student.age); // 21
console.log(proxy.age); // 22
2. set()

set 方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象 target属性名 key属性值 value代理对象 proxy (可选)。

const student = { name: "ienyh", age: 21, gender: "male" };
const proxy = new Proxy(student, {
  set(target, key, value) {
    if (key === "age") {
      if (value < 0) {
        target[key] = 0;
      } else {
        target[key] = value;
      }
    } else {
      target[key] = value;
    }
  },
});

proxy.age = -21;
console.log(student); // { name: 'ienyh', age: 0, gender: 'male' }
proxy.age = 12;
console.log(student); // { name: 'ienyh', age: 12, gender: 'male' }

可撤销的代理

使用new Proxy() 创建的普通代理是不可撤销的,普通代理对象和目标对象之间的练习是一直存在的,但有时候我们是需要切断这种联系的。

我们可以使用 Proxy 类的静态方法 Proxy.revocable() 来生成一个可以撤销的代理对象

撤销函数和代理对象是在实例化时同时生成

const { proxy, revoke } = Proxy.revocable(target, handler);
// 需要撤销代理时直接调用 revoke() 就可以了
revoke()

撤销代理之后就不可以再调用 proxy 对象了,不然会报错 TypeError

反射(Reflect [rɪ’flekt]


概念

处理程序对象 handler 中所有的捕获器都有对应的反射方法,这些方法与捕获器拦截的方法具有相同的名称函数签名,而且与被拦截方法的行为也一致。不过反射 API 可不止限于在处理程序中使用。

其实反射就是一个对象,不是一个构造方法,对象上有很多方法,类似于 Math 对象。

Object.prototype 上有的很多方法,Reflect 对象上也有。

通常来说,Object原型上的方法适用于通用程序,而反射方法更适用于细粒度的对象控制与操作

我们可以轻松的应用它们:

const student = { name: "ienyh", age: 21, gender: "male" };
const proxy = new Proxy(student, {
  get(target, key) {
    console.log("[get]:", key);
    return Reflect.get(...arguments);
  },
}

proxy.age; // [get]: age

反射 API

反射 API 与处理程序对象 handler 中的捕获器对应的也共有 13 种。

反射 API参数返回值拦截的操作
Reflect.get()(target, property, receiver)返回值无限制
Reflect.set()(target, property, value, receiver)boolean
Reflect.has()(target, property)boolean
Reflect.defineProperty()(target, property, descriptor)boolean
Reflect.getOwnPropertyDescriptor()(target, property)对象
Reflect.deleteProperty()(target, property)boolean
Reflect.ownKeys()(target)包含字符串或符号的可枚举对象
Reflect.getPrototypeOf()(target)对象或 null
Reflect.setPrototypeOf()(target, property)boolean
Reflect.isExtension()(target)boolean
Reflect.preventExtensions()(target)boolean
Reflect.apply()(target, thisArg, argumentList)返回值无限制
Reflect.construct()(target, argumentList, newTarget)必须返回一个对象
Reflect.get()
Reflect.set()
Reflect.has()
Reflect.defineProperty()
Reflect.getOwnPropertyDescriptor()
Reflect.deleteProperty()
Reflect.ownKeys()
Reflect.getPrototypeOf()
Reflect.setPrototypeOf()
Reflect.isExtension()
Reflect.preventExtensions()
Reflect.apply()
Reflect.construct()

优先使用代理的情况

1. 状态标记

许多反射方法的返回值是一个 boolean 值,可以用于我们判断操作是否成功状态标记

const ienyh = { name: "ienyh" };

Object.freeze(ienyh); // 使用 Object.freeze() 将对象冻结,下面的操作将会失败

if (Reflect.defineProperty(ienyh, "age", { value: 21 })) {
  console.log("设置成功");
} else {
  console.log("设置失败"); // 将会打印
}

一下反射方法会提供状态标记:

  • Reflect.set()
  • Reflect.defineProperty()
  • Reflect.preventExtions()
  • Reflect.setPrototypeOf()
  • Reflect.deleteProperty()
2. 用反射方法代替操作符
反射方法可以替代的操作符
Reflect.get()对象属性访问
Reflect.set()= 赋值操作符
Reflect.has()inwith()
Reflect.deleteProperty()delete
Reflect.construct()new
3. 安全应用

比如在通过 apply() 调用函数时,被调用的函数可能定义了自己的 apply() 属性(可能性较小),为了避免就需要这么写:

Function.prototype.apply.call(Func, thisObj, argumentList);

但这样的写法过于“糟糕”,我们就需要一个简单的反射方法:

Reflect.apply(Func, thisObj, argumentList);

总结 & Last


代理和反射的应用场景是不可限量的,开发者可以用它创建出各种各样的编码模式,包括(远远不限于)跟踪属性访问隐藏属性组织修改或删除属性函数参数验证构造函数参数验证数据绑定、以及可观察对象

欢迎在评论区交流学习,一起学习哈哈哈 👨‍💻!

点个赞,点个关注吧 😆。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值