Proxy 代理对象使用详解即原理总结

news2024/12/26 20:52:15

Proxy 代理对象使用详解即原理总结

Proxy简单介绍

ECMAscript 6新增的代理可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用,在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。代理对象可以作为目标对象的替身,但又完全独立于目标对象。(摘自Javascript高级程序设计)

我的理解就是:Proxy就是使用者和目标对象之间的一个中间商,使用者和对象之间的操作需要中间商代为完成,并且在完成使用者对对象的操作的前提下,中间商可以多加一点操作达到自己的目的。

重点

  1. Proxy 只能代理对象
  2. 代理指的是对一个对象基本语义的代理。读取和设置对象的属性都是基本语义,因为它们是一步就可以完成的操作。而调用一个对象内的函数就不是基本操作,因为它需要两步操作,第一步先拿到函数属性,第二步执行。
  3. 不能用 instanceof 判断代理对象的类型,因为 Proxy.prototype 是 undefined。
  4. 代理也可以代理另外一个代理形成多层拦截,如下例子:
let obj = {name:'孤城浪人'}
let proxy = new Proxy(obj,{
  get(target,property){
    console.log('第一层代理触发');
    return target[property];
  }
});
let proxy1 = new Proxy(proxy,{
  get(target,property){
    console.log('第二层代理触发');
    return target[property];
  }
});
console.log(proxy1.name);

在这里插入图片描述

Proxy 基本使用

代理对象 Proxy 通过 new 操作符实例化,实例化时要传入两个参数,参数一:目标对象;参数二:处理程序对象(捕获器),这两个参数是必须的,少任何一个都会报错。

创建空代理

可以看到在实例化 Proxy 时,传入的处理程序对象为空对象,也就是说本次代理对象(中间商)不会私自做任何操作。

const obj = {
  name: "孤城浪人",
  age: 18,
  sex: "男",
};
const ProxyObj = new Proxy(obj, {});

简单代理

get 拦截参数:

  1. 要操作的目标对象,下面的例子是 obj;
  2. 要操作的属性名;
  3. 代理对象(可选);

set 拦截参数:

  1. 要操作的目标对象,下面的例子是 obj;
  2. 要操作的属性名;
  3. 新值;
  4. 接收最初赋值的对象(可选)。
const obj = {
  name: "孤城浪人",
  age: 18,
  sex: "男",
};
const ProxyObj = new Proxy(obj, {
  get(target, property, ProxyTarget) {
    console.log(`get触发,读取${property}属性`);
    return target[property];
  },
  set(target, property, value, receiver) {
    console.log(`set触发,赋值${property}属性,最初赋值对象:`,receiver);
    target[property] = value;
  },
});
console.log(ProxyObj.name); 
console.log(ProxyObj.age);
ProxyObj.sex = "女";

结果
在这里插入图片描述

注意:在 set 的时候,输出了第四个参数为代理对象 ProxyObj,这是因为我的赋值语句就是赋值给 ProxyObj.sex,所以 ProxyObj 就是最初赋值的对象。

捕获器不变式

每个捕获器都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”,防止捕获器定义出现过于反常的行为(摘自Javascript高级程序设计)。

我的理解就是在给对象添加属性时可以给它添加描述限制属性的行为,那么我们在捕获器里设置的操作就不能违背该属性的描述

例:

const obj = {
  name: "孤城浪人",
};
Object.defineProperty(obj, "age", {
  writable: false,//不可写
  value: 18,
});
const ProxyObj = new Proxy(obj, {
  get(target, property, ProxyTarget) {
    console.log(`get触发,读取${property}属性`);
    return 12;
  },
});
console.log(ProxyObj.name); //12
console.log(ProxyObj.age); // 报错

在这里插入图片描述

由例子的结果可知,当我给对象添加不可写(不能更改属性值)的属性 age=18 时,但是我在代理中返回值是 12 ,与属性值不同,就相当于更改了属性值,这时就会报错。

可撤销代理

既然可以代理对象,那么也可通过 Proxy 暴露的 revocable() 撤销代理对象与目标对象的联系,并且撤销操作不可逆的。撤销函数 revoke() 是幂等的。在撤销之后再调用代理对象会报错。如下例子:

const obj = {
  name: "孤城浪人",
};
const { proxy, revoke } = Proxy.revocable(obj, {
  get(target, property, ProxyTarget) {
    console.log(`get触发,读取${property}属性`);
    return 12;
  },
});
console.log(proxy.name);
revoke();// 撤销代理
console.log(proxy.name);

在这里插入图片描述

代理的 this 问题

当使用 Proxy 代理对象时,目标对象内部的 this 会自动改变为 Proxy 代理对象。也就是 this 变成了 Proxy 代理对象。

const obj = {
  name: "孤城浪人",
  test() {
    console.log(this === proxyObj);
  },
};
const proxyObj = new Proxy(obj, {
  get(target, property) {
    return target[property];
  },
});
obj.test();//false
proxyObj.test();//true

当通过代理对象调用 test 方法时,this 就指向了代理对象 proxyObj,这很合理,因为调用对象的属性函数,那么该函数的 this 就指向该实例。

再来看一个应用的例子:

const obj = {
  _name: "孤城浪人",
  get name() {
    console.log(this)
    return this._name;
  },
};
const proxyObj = new Proxy(obj, {
  get(target, property, receiver) {
    return target[property];
  },
});
const obj1 = {
  __proto__:proxyObj,
  _name:'李四'
}
console.log(obj.name);
console.log(obj1.name);

在这里插入图片描述

当我把 obj1 对象的原型设置为 proxyObj 时,再输出obj1.name,obj1 对象没有该属性,所以会到原型对象 proxyObj 上找,继而会触发 obj 对象的读取 name 的钩子,但是由结果可以看到两次输出 get 钩子的 this 都是 obj,这显然不合理。

此时就需要用到 Reflect 对象来救场了,具体怎么使用可以看这篇文章《你能懂的 JS 之 Reflect 反射》。

捕获器

Proxy 代理可以捕获 13 种不同的基本操作,如下表(参考文章:JS进阶 | Proxy代理对象)

对象中的方法对应触发条件
handler.getPrototypeOf()Object.getPrototypeOf 方法的捕获器
handler.setPrototypeOf()Object.setPrototypeOf 方法的捕获器
handler.isExtensible()Object.isExtensible 方法的捕获器
handler.preventExtensions()Object.preventExtensions 方法的捕获器
handler.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor 方法的捕获器
handler.defineProperty()Object.defineProperty 方法的捕获器
handler.has()in 操作符的捕获器
handler.get()属性读取操作的捕获器
handler.set()属性设置操作的捕获器
handler.deleteProperty()delete 操作符的捕获器
handler.ownKeys()Object.getOwnPropertyNames方法和 Object.getOwnPropertySymbols 方法的捕获器
handler.apply()函数被apply调用操作的捕获器
handler.construct()new 操作符的捕获器

关于对象的所有操作都是由这十三种基本操作或它们中的部分组合完成,所以在代理是只要拦截其中一个就能正常工作了。

Proxy 工作原理

我们知道 JS 中一切皆对象,即便函数也是一个对象,并且 Proxy 代理的目标也是对象,但是你真的了解 JS 中的对象吗?

ECMAscript 规范中规定 JS 中有两种对象,常规对象和异质对象(了解即可),任何不属于常规对象的对象都属于异质对象。并且对象的实际语义是由对象的内部方法(内部方法就是我们操作一个对象时引擎内部调用的方法)指定的

例:读取obj.name的值时,引擎内部会调用[[Get]]内部方法读取属性值。这里的[[xxx]]也叫内部槽

ECMAscript 规范要求的所有必要的内部方法如下(摘自 《Vue设计与实现》):

对象必要的内部方法

内部方法描述
[[GetPrototypeOf]]查明为该对象提供继承属性的对象, null 代表没有继承属性
[[SetPrototypeOf]]将该对象与提供继承属性的另一个对象相关联。传递 null 表示没有继承属性,返回 true 表示操作成功完成,返回 false 表示操作失败
[[IsExtensible]]查明是否允许向该对象添加其他属性
[[PreventExtensions]]控制能否向该对象添加新属性。如果操作成功则返回 true ,如果操作失败则返 false
[[GetOwnPropertyDescriptor]]返回该对象自身属性的描述符,其键为 propertyKey ,如果不存在这样的属性,则返回 undeftned
[[DefineProperty]]创建或更改自己的属性,其键为 propertyKey ,以具有由 PropertyDescriptor 描述的状态。如果该属性已成功创建或更新,则返回 true ;如果无法创建或更新该属性,则返 false
[[HasProperty]]返回一个布尔值,指示该对象是否已经拥有键为 propertyKey 的自己的或继承的属性
[[Get]]从该对象返回键为 propertyKey 的属性的值。如果必须运行 ECMAScript 代码来检索属性值,则在运行代码时使用 Receiver 作为 this 值
[[Set]]将键值为 propertyKey 的属性的值设置为 value 。如果必须运行 ECMAScript 代码来设置属性值,则在运行代码时使用 Receiver 作为 this 值。如果成功设置了属性值,则返回 true ;如果无法设置,则返回 false
[[Delete]]从该对象中删除属于自身的键为 propertyKey 的属性。如果该属性未被删除并且仍然存在,则返回 false ;如果该属性已被删除或不存在,则返回 true
[[OwnKeys]]返回一个 List ,其元素都是对象自身的属性键

额外的必要内部方法

内部方法描述
[[Call]]将运行的代码与 this 对象关联。由函数调用触发。该内部方法的参数是一个 this 值和参数列表
[[Construct]]创建一个对象。通过 new 运算符或 super 调用触发。该内部方法的第一个参数是一个 List ,该 List 的素是构造幽数调用或 super 调用的参数,第二个参数是最初应用 new 还算符的对象。实现该内部方法的对象称为构造函数

了解了这些就可以谈谈如何区分常规对象和异质对象(不是重点,了解即可)

  • 对于对象必要的内部方法,必须使用 ECMA 规范10.1.x节给的定义实现。
  • 对于内部方法[[Call]]必须使用 ECMA 规范10.2.1节给出的定义实现。
  • 对于内部方法[[Construct]]必须使用ECMA规范10.2.2节给出的定义实现。

所有不满足这三点要求的对象都是异质对象。

好了,现在可以聊聊 Proxy 的工作原理了。因为 Proxy 本质上是异质对象,所以 Proxy 也部署了必要的内部方法,与常规对象不同的是实例代理对象时指定的处理程序对象,实际上是用来自定义代理对象本身的内部方法和行为逻辑的。这意味着我们在引擎内部添加了自己的逻辑。

let obj = {name:'孤城浪人'};
let proxy = new Proxy(obj,{
  get(target,property){
    console.log('触发拦截');
    return target[property];
  }
})
console.log(proxy.name);
proxy.name = '孤城浪人wpf';
console.log(obj.name);

在这里插入图片描述

分析上面的代码和结果:在实例 proxy 代理对象时,指定了 proxy 对象的内部方法[[Get]]的逻辑,所以在调用proxy.name时,浏览器引擎会自动调用 proxy 的[[Get]]方法,于是输出了“触发拦截”,返回的实际上是obj.name,又会调用 obj 对象的[[Get]]方法,拿到值“孤城浪人”。

如果创建代理对象时没有指定处理程序对象,那么代理对象的内部方法会直接调用原始对象的内部方法。这意味着即使我们没有给代理对象指定某些内部方法,程序也能正常使用。

还是分析上面的代码:代理对象没有指定 proxy 的内部方法[[set]]的逻辑,所以在执行proxy.name = '孤城浪人wpf';时调用 proxy [[set]],它会直接调用 obj 的 [[set]]方法,直接输出obj.name可以看到属性值已经修改了。

小结

小结一下 Proxy 工作流程:我们指定的程序处理对象会变成 Proxy 对应内部方法的逻辑,所以当我们操作代理对象时,引擎会自动调用并执行对应内部方法(我们自定义的逻辑),自定义的内部方法又会调用原始对象的对应内部方法,继而完成对原始对象的操作。并且由于对象的所有操作都是直接调用内部方法或由几个内部方法组合完成,所以只要指定对应拦截函数并不会出现拦截不到的情况。Vue3 的响应式系统就是利用 Proxy 在引擎内部自定义逻辑的能力。

总结

ECMAscript 6规范算是JS的一个里程碑,它的改动非常大,新增了很多内容,像本文的 Proxy 就是新增的内容。总的来说 Proxy 就是一个异质对象,我们在处理程序对象中指定的拦截函数会变为它的内部方法的逻辑,因此引擎会自动调用,就实现了拦截处理。

我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/20971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python与数据分析】Matplotlib数据可视化

目录 前言 一、数据可视化库matplotlib 1.综述 2.pyplot基础语法 (1)创建画布与创建子图 (2)添加画布内容 (3)保存与展示图形 (4)设置pyplot的动态rc参数 二、绘制折线图 …

Python面向对象总结一

还记得第一次接触Python是在刚刚进入大学的第一个学期,之后就没有再使用过python。虽然基本语法,内置函数等没有忘记,但最近的学习中,一直在用python、matlab混合编程,零零散散的知识点、模模糊糊的记忆,有…

5G无线技术基础自学系列 | 传统无线网络架构

素材来源:《5G无线网络规划与优化》 一边学习一边整理内容,并与大家分享,侵权即删,谢谢支持! 附上汇总贴:5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 在4G网络中, 无线侧基本完成…

Anaconda配置镜像源

目录 一、首先生成 Anaconda的 .condarc配置文件: 二、命令行方式配置镜像源 三、可视化界面的方式配置镜像源 一、首先生成 Anaconda的 .condarc配置文件: 打开 Anaconda prompt,敲命令: conda config 查看 .condarc 中的源…

MySQL 增删改查进阶 — 数据库约束

文章目录数据库约束1.约束类型1.1 not null1.2 unique 唯一约束1.3 主键约束 primary key1.4 default 默认值1.5 外键约束 foreign key数据库约束 约束就是让数据库帮助程序猿更好的检查数据是否正确。 1.约束类型 not null - 提示某列不能存储 NULL 值。 允许为空&#xff…

wav to image concat 版

🍿*★,*:.☆欢迎您/$:*.★* 🍿 目录 背景 正文 总结 背景描述

STM32矩阵按键

矩阵按键原理 本实验使用STM32F103单片机,主题代码通用的。 如果每个按键占用一个GPIO引脚,对于使用多个按键来说就是一种资源的浪费,因此当我们在设计时,可以考虑矩阵这种方式。本实验使用4*4也就是16个按键。 矩阵按键相对于独立…

Go 语言搭建个人博客(qiucode.cn 重构篇 三)

1、读取配置文件 项目中如数据库连接、邮箱配置等这些信息一般会被写入一个文件,而通过编码在程序中读取想要的配置信息。 本项目使用yaml文件作为配置文件,配合第三方库viper来读取yaml配置文件。 go get -u github.com/spf13/viper在项目根目录新建一个yaml文件,内容如…

数学杂谈:限制条件下的均匀分布考察

数学杂谈:限制条件下的均匀分布考察 1. 问题描述2. 问题解答 1. 答案2. 解析3. 蒙特卡洛模拟 3. 离散情况延拓 1. 正整数的情况2. 整数的情况3. N→∞N \to \inftyN→∞的情况 4. 误区分析 1. 问题描述 假设x1,...,xnx_1, ..., x_nx1​,...,xn​均为0∼10 \sim 10…

思科防火墙解析(ASA)

♥️作者:小刘在C站 ♥️每天分享云计算网络运维课堂笔记,一起努力,共赴美好人生! ♥️夕阳下,是最美的,绽放。 目录 一. 防火墙的分类, 二. 发展史, 三. 思科防火墙技术应用的…

[附源码]java毕业设计铁东社区新冠病毒疫苗接种管理系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

山东大学线性代数-4-线性方程组

目录 4.1 齐次线性方程组 4.1.1 齐次线性方程组的定义 4.1.2 方程组的三种形式 4.1.3 齐次线性方程组解的性质 4.1.4 行最简形矩阵 4.1.5 两个例题 4.2 基础解系的求法 4.2.1 求解步骤 4.2.2 例题 4.3 非齐次线性方程组 4.3.1 相关概念 4.3.2 非齐次线性方程组的有…

编译安装php扩展

1. 切换到要安装的扩展的目录下 2. 执行phpize(在扩展目录里 可以写自己电脑里的绝对路径) ../../bin/phpize 3. 然后执行 ./configure ./configure 4. 没问题的话 make 然后 make install make 运行之后出现下图就说明可以下一步了 运行 make install make install 出现…

GitHub使用教程

目录概要一. 下载git二. 初始化本地仓库设置签名查看状态,三大分区,添加,提交操作查看状态,三大分区添加查看提交历史查看提交历史的其他指令如何进行版本切换基于索引值基于^符号进行版本切换基于~符号进行版本切换reset指令的参…

【OS】新国立nus操作系统知识点(中文版)

文章目录1. Introduction to OS本章涉及1.1 什么是操作系统?1.2 为什么我们需要操作系统?抽象 Abstraction控制程序Summary1.3 现代操作系统分类1.4 操作系统结构OS结构OS是一个程序OS的实现单片OS Monolithic OS微核OS Microkernel虚拟机 Virtual Machi…

22071.11.20作业

在串口工具进行输入: echo 1 > /dev/myled0 ---->led1灯点亮 echo 0 > /dev/myled0 ---->led1灯熄灭 echo 1 > /dev/myled1 ---->led2灯点亮 echo 0 > /dev/myled1 ---->led2灯熄灭 echo 1 > /dev/myled2 ----&g…

项目实战——创建个人中心页面(上)

ps:本篇文章不涉及复杂代码编写,放心食用~~ 目录 一、整体框架 二、创建新表 bot 三、实现后端API 1、连接数据库和后端 2、实现 增删改查 API 1、增加一个 Bot 2、删除一个 Bot 3、修改一个 Bot 4、查询 Bot 列表 一、整体框架 二、创建新表 bo…

攻防世界nice_bgm

nice_bgm 题目描述:我拿出自己的私密音乐来和你分享,一起享受快乐吧 题目环境:https://download.csdn.net/download/m0_59188912/87097729 private bit隐写,直接用python脚本跑。 脚本源码: import re import binascii…

光线追踪与全域光渲染keyshot中文

keyshot可以快速、轻松地创神奇的渲染和动画效果,支持Mac和PC上的多种3D文件格式。它可以实时查看效果,使用方便,可以更快地创造视觉效果;材料超越了材料的外观,为高质量的视觉效果提供了科学准确的性能,使…

babel:无法将“babel“项目识别问题

全局安装babel命令 npm install --global babel-cli 局部安装babel命令 npm install --save-dev babel-cli 你安装后可能会出现的问题: 可能存在原因: ① 权限不够。 ②前面需改了node的global配置 --------------------------------------------…