proxy详细介绍与使用
proxy详细介绍与使用
proxy
对象用于创建一个对象的代理,是在目标对象之前架设一个拦截,外界对该对象的访问,都必须先通过这个拦截。通过这种机制,就可以对外界的访问进行过滤和改写。
ES6
原生提供 Proxy
构造函数,用来生成 Proxy
实例。
var proxy = new Proxy(target, handler);
target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。
const obj = {};
const proxy=new Proxy(obj,{get:function(target,propKey){return 1}
})console.log(proxy.name) // 1
console.log(proxy.value) // 1
在上面代码中,Proxy
接受两个参数,第一个是要代理的目标对象(上面的是空对象obj
),在没有设置Proxy
的情况下,对proxy
的操作就是访问obj
。第二个参数是配置对象,对每个被代理的操作,需要提供一个对应的处理函数,这个函数用于拦截对应的操作。
上面代码中,配置对象有个get
方法,用来拦截对目标对象属性的访问请求。get
方法中的两个参数分别是目标对象和所要访问的属性。由于设置的是返回1,所以访问任何属性都得到1。
如果handler
没有设置拦截,就等同于直接通向原对象。
const obj = {};
const proxy=new Proxy(obj, {})
proxy.a = 1
console.log(obj.a) // 1
console.log(obj === proxy) // false
handler
是空对象,没有拦截作用,访问proxy
等同于访问obj
。但是代理对象和原对象并不相等。
handler
配置
handler
对象是一个容纳一批特定属性的占位符对象。它包含有 proxy
的各个捕获器。
1. get
get
方法用于拦截某个属性的读取操作,接受三个参数,(目标对象,属性名,Proxy
或者proxy
实例本身),第三个参数可选。
const obj={value:1
}
const proxy=new Proxy(obj,{get:function(target,propKey){if(propKey in target){return target[propKey]}else{throw new ReferenceError(`${propKey} 不存在`)}}
})
console.log(proxy.value) // 1
proxy.name //报错
如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined
。
get
方法可以被继承
const obj = {a: 1
};
let proto = new Proxy(obj, {get(target, propertyKey) {return target[propertyKey];}
});
let newObj = Object.create(proto);
newObj.a // "GET foo"
拦截操作定义在原型对象上面,所以如果读取obj
对象继承的属性时,拦截会生效。
下面是一个get
方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy
实例。
const proxy = new Proxy({}, {get: function(target, key, receiver) {return receiver;}
});
proxy.getReceiver === proxy // true
对于
get
方法,proxy
存在一些约束
- 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同,否则会抛出异常。
const obj = {}; Object.defineProperty(obj, "a", {configurable: false,enumerable: false,value: 1,writable: false }); const proxy=new Proxy(obj,{get:function(target,propKey){return 2 // return 1 就没问题} }); proxy.a
- 如果要访问的目标属性没有配置访问方法,即
get
方法是undefined
的,则返回值必须为undefined
。const obj = {}; Object.defineProperty(obj, "a", {configurable: false,get: undefined }); const proxy=new Proxy(obj,{get:function(target,propKey){return 1; // return undefined 就没问题} }); proxy.a
2. set
用来拦截某个属性的赋值操作,接受四个参数(目标对象、属性名、属性值、最初被调用的对象(通常是proxy
实例本身,但 handler
的 set
方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy
本身))),第四个参数可选。
const proxy = new Proxy({}, {set: function(obj,prop,value){if(prop === 'value') {if(!Number.isInteger(value)){throw new TypeError('不是整数')}if(value>10){throw new RangeError('太大了')}}obj[prop] = value}
})
proxy.value = 5
proxy.value = 'val' //报错
proxy.value = 20 //报错
由于设置了存值函数set
,任何不符合要求的value
属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。
来看看set
在原型链上的情况.
const proxy = new Proxy({}, {set: function(obj, prop, value, receiver) {console.log(receiver === newObj); // trueif(prop === 'value') {if(!Number.isInteger(value)){throw new TypeError('不是整数')}if(value>10){throw new RangeError('太大了')}}obj[prop] = value}
})
let newObj = Object.create(proxy);
newObj.value = 1;
将代理对象作为原型创建一个新对象,此时第四个参数就是这个新对象而不是proxy
实例。
对于
set
方法,proxy
也存在一些约束
- 若目标属性是一个不可写及不可配置的数据属性,则不能改变它的值。
const obj = {}; Object.defineProperty(obj, "a", {configurable: false,enumerable: false,value: 1,writable: false }); const proxy=new Proxy(obj,{set: function(obj, prop, value) {obj[prop] = value} }); proxy.a = 1; proxy.a // 还是 undefined
- 如果目标属性没有配置存储方法,即
set
的是undefined
,则不能设置它的值.const obj = {}; Object.defineProperty(obj, "a", {configurable: false,set: undefined }); const proxy=new Proxy(obj,{set: function(obj, prop, value) {obj[prop] = value} }); proxy.a = 1; proxy.a // 还是 undefined
- 在严格模式下,如果
set
方法返回false
,那么也会抛出一个异常。
3. apply
拦截函数的调用,接受三个参数,分别是目标对象、目标对象的上下文对象(this
)和目标对象的参数数组。
var target = function () { return 'target'; };
var handler = {apply: function () {return 'proxy';}
};var p = new Proxy(target, handler);
p()
// "proxy"
变量p
是 Proxy
的实例,当它作为函数调用时(p()
),就会被apply
方法拦截,返回一个字符串。
4. has
用来拦截HasProperty
操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in
运算符。接受两个参数,分别是目标对象、需查询的属性名。
var handler = {has (target, key) {return key in target;}
};
var target = { prop: 'foo' };
var proxy = new Proxy(target, handler);
'propa' in proxy // false
如果原对象不可配置或者禁止扩展,这时has
拦截会报错。
var obj = { a: 10 };
Object.preventExtensions(obj);var p = new Proxy(obj, {has: function(target, prop) {return false;}
});
'a' in p // TypeError is thrown
has
方法拦截的是HasProperty
操作,而不是HasOwnProperty
操作,即has
方法不判断一个属性是对象自身的属性,还是继承的属性。可以看作是针对 in
操作的钩子。
5. construct
用于拦截new
命令
var handler = {construct (target, args, newTarget) {return new target(...args);}
};
接受三个参数,目标对象,构造函数的参数对象,new
命令作用的构造函数。
construct
方法返回的必须是一个对象,否则会报错。
6. deletePrototype
拦截delete
操作,如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除。
var handler = {deleteProperty (target, key) {invariant(key, 'delete');delete target[key];return true;}
};
function invariant (key, action) {if (key[0] === '_') {throw new Error(`Invalid attempt to ${action} private "${key}" property`);}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property
deleteProperty
方法拦截了delete
操作符,删除第一个字符为下划线的属性会报错。
目标对象自身的不可配置(configurable
)的属性,不能被deleteProperty
方法删除,否则报错。
7. defineProperty
拦截了Object.defineProperty
操作。
var handler = {defineProperty (target, key, descriptor) {return false;}
};
var proxy = new Proxy({}, handler);
proxy.a = 'a' // 不会生效
8. getOwnPropertyDescriptor
拦截Object.getOwnPropertyDescriptor()
,返回一个属性描述对象或者undefined
。
var handler = {getOwnPropertyDescriptor (target, key) {return Object.getOwnPropertyDescriptor(target, key);}
};
var target = {a: 'a' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'a')
// { value: 'a', writable: true, enumerable: true, configurable: true }
9. getPrototypeOf
用来拦截获取对象原型。
var proto = {};
var p = new Proxy({}, {getPrototypeOf(target) {return proto;}
});
Object.getPrototypeOf(p) === proto // true
10. isExtensible
拦截Object.isExtensible
操作。
var p = new Proxy({}, {isExtensible: function(target) {console.log("called");return true;}
});
Object.isExtensible(p)
// "called"
// true
11. ownKeys
拦截对象自身属性的读取操作。
let target = {a: 1,b: 2,c: 3
};
let handler = {ownKeys(target) {return ['a'];}
};
let proxy = new Proxy(target, handler);
Object.keys(proxy)
// [ 'a' ]
12. preventExtensions
拦截Object.preventExtensions()
。
var proxy = new Proxy({}, {preventExtensions: function(target) {return true;}
});
Object.preventExtensions(proxy)
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
13. setPrototypeOf
用来拦截Object.setPrototypeOf
方法。
var handler = {setPrototypeOf (target, proto) {throw new Error('Changing the prototype is forbidden');}
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden
只要修改target
的原型对象,就会报错。
proxy
比Object.defineProperty
具有的优势
了解Proxy
的基本用法过后接下来我们再深入探讨一下相比于Object.defineProperty
, Proxy
到底有哪些优势。
首先最明显的优势就是在于Proxy
要更为强大一些,那这个强大具体体现在Object.defineProperty
只能监听到对象属性的读取或者是写入,而Proxy
除读写外还可以监听对象中属性的删除,对对象当中方法的调用等等。
这里我们为obj
对象定义一个Proxy
对象,在Proxy
对象的处理对象中的外的添加一个deleteProperty
的代理方法,这个方法会在外部对当前这个代理对象进行delete
操作时会自动执行。
这个方法同样接收两个参数,分别是代理目标对象和所要删除的这个属性的名称。
const obj = {a: 1
}
const proxy = new Proxy(obj, {deleteProperty(target, propKey) {delete target[propKey]}
})delete proxy.a
vue2.x
的响应式并不具备这样的处理,而是需要一个$delete
方法来解决。
export default {data() {return {obj: {a: 1}}},methods: {deleteA() {delete this.obj.a; // a属性确实删除了,但是没有触发对应的更新// this.$delete(this.obj, 'a') 能删除a,并触发更新}}
}
比如上方的使用delete
直接删除a
,并不会触发更新,这是因为Object.defineProperty
只能监听到对象属性的读取或者是写入,没办法监听删除。我们顺便来看看vue2.x
的解决办法:
function del(target: any[] | object, key: any) {// ...if (isArray(target) && isValidArrayIndex(key)) {target.splice(key, 1)return}const ob = (target as any).__ob__// ...delete target[key]if (!ob) {return}// 获取对象的观察者__ob__,并手动调用更新if (__DEV__) {ob.dep.notify({type: TriggerOpTypes.DELETE,target: target,key})} else {ob.dep.notify()}
}
可以看到 vue
实例的 $delete
处理了一些边界情况,处理了数组和对象的删除,最重要的是,获取了变量上的ob
指针指向的观察者实例,并通知依赖dep
里的watcher
更新。
第二点优势就是对于数组对象进行监视,
通常我们想要监视数组的变化,基本要依靠重写数组方法,这也是vue2.x
的实现方式,proxy
可以直接监视数组的变化。以往我们想要通过Object.defineProperty
去监视数组的操作最常见的方式是重写数组的操作方法,大体的方式就是通过自定义的方法去覆盖掉数组原型对象上的push
,shift
之类的方法,以此来劫持对应的方法调用的过程。
这里来看如何直接使用Proxy
对象来对数组进行监视。这里我们定义一个list
数组,然后对这个list
数组进行Proxy
监视。
在这个Proxy
对象的处理对象上我们去添加一个set
方法,用于监视数据的写入,在这个方法的内部我们打印参数的值,然后再target
对象上设置传入的值,最后返回一个true
表示写入成功。
这样我们再外部对数组的写入都会被监视到,例如我们这里通过push
向数组中添加值。
const arr = []
const proxy = new Proxy(arr, {set(target, propKey, value) {target[propkey] = value;return true}
})
Proxy
内部会自动根据push
操作推断出来他所处的下标,每次添加或者设置都会定位到对应的下标property
。
最后相比于Object.defineProperty
还有一点优势就是,Proxy
是以非入侵的方式监管了对象的读写,那也就是说一个已经定义好的对象我们不需要对对象本身去做任何的操作,就可以监视到他内部成员的读写,而defineProperty
的方式就要求我们必须按特定的方式单独去定义对象当中那些被监视的属性。
相关文章:

proxy详细介绍与使用
proxy详细介绍与使用 proxy 对象用于创建一个对象的代理,是在目标对象之前架设一个拦截,外界对该对象的访问,都必须先通过这个拦截。通过这种机制,就可以对外界的访问进行过滤和改写。 ES6 原生提供 Proxy 构造函数,…...

基于YOLOv5的舰船检测与识别系统(Python+清新界面+数据集)
摘要:基于YOLOv5的舰船检测与识别系统用于识别包括渔船、游轮等多种海上船只类型,检测船舰目标并进行识别计数,以提供海洋船只的自动化监测和管理。本文详细介绍船舰类型识别系统,在介绍算法原理的同时,给出Python的实…...

【C#】List数据去重
系列文章 【C#】单号生成器(定义编号规则、流水号、产生业务单号) 本文链接:https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成(构建本周开始、结束日期) 本文链接:https…...

避免踩坑,教给你VSCode中最常用到的6项功能
这里为程序员介绍VSCode中包含的许多令人兴奋的Tips。 1. 插件市场中免费下载使用CodeGeeX插件 AI辅助编程工具CodeGeeX,是完全免费,开源开放给所有开发者使用。程序员普遍反应使用这个插件后,代码编写效率提升2倍以上。 CodeGeeX插件拥有…...

ThingsBoard开源物联网平台智慧农业实例快速部署教程(Ubuntu、CentOS适用)
ThingsBoard部署教程文档 文章目录ThingsBoard部署教程文档1. JDK环境安装2. 安装thingsBoard2.1 ThingsBoard软件包安装2.2 PostgreSQL安装2.3 PostgreSQL初始化配置3. 修改ThingsBord的配置4. 运行安装脚本测试5. 访问测试6. 导入一个仪表盘库6.1 导出仪表盘并导入自己的项目…...

【Java Spring基本问题】记录面试题宝典中自己不熟悉的Spring问题
文章目录Spring Bean定义装配Spring Bean生命周期Spring Bean容器Spring 循环依赖Spring 事务Autowired和ResourceSpring Bean定义装配 参考文章 1. 定义Spring Bean的三种方式 XML文件定义Spring Bean JavaConfig定义Spring Bean Component注解定义SpringBean 2. 装配Spri…...

I2C协议简介 Verilog实现
I2C协议 IIC 协议是三种最常用的串行通信协议(I2C,SPI,UART)之一,接口包含 SDA(串行数据线)和 SCL(串行时钟线),均为双向端口。I2C 仅使用两根信号线…...

服务器被DDoS攻击,怎么破?
文章目录前言网站受到DDoS的症状判断是否被攻击查看网络带宽占用查看网络连接TCP连接攻击SYN洪水攻击防御措施TCP/IP内核参数优化iptables 防火墙预防防止同步包洪水(Sync Flood)Ping洪水攻击(Ping of Death)控制单个IP的最大并发…...

实现完全二叉树
文章目录1、树概念及结构2、孩子兄弟表示法3、二叉树3.1、二叉树的概念3.2、特殊的二叉树3.3、二叉树的存储4、堆的性质5、数组结构实现完全二叉树1、结构体的定义2、初始化堆3、销毁堆4、交换函数5、向上调整函数6、插入数据7、向下调整函数8、删除堆顶数据函数9、判断是否空堆…...

【独家】华为OD机试 - 矩阵最值(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本期题目:矩阵最值 题目 给定一个仅包…...

C++模板(进阶)
文章目录非类型模板参数类模板的特化类模板的概念函数模板特化类模板的特化全特化偏特化参数的进一步限制模板的分离编译模板的优缺点非类型模板参数 模板参数分类型形参与非类型形参. 类型形参: 出现在模板参数列表中,跟在class,typename之类的参数类型名称. 非类型形参: 就是…...

【数据分析之道(二)】列表
文章目录专栏导读1、列表介绍2、访问列表中的值3、列表增加和修改4、删除元素5、列表函数6、列表方法专栏导读 ✍ 作者简介:i阿极,CSDN Python领域新星创作者,专注于分享python领域知识。 ✍ 本文录入于《数据分析之道》,本专栏针…...

架构师必须要掌握的大小端问题
一、什么是大端和小端 所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 简单来说:大端——高尾端,小端——低尾端 举个例子,比如数字 0x12 34 56 78…...

2023年ACM竞赛班 2023.3.20题解
目录 瞎编乱造第一题 瞎编乱造第二题 瞎编乱造第三题 瞎编乱造第四题 瞎编乱造第五题 不是很想编了但还是得编的第六题 不是很想编了但还是得编的第七题 还差三道题就编完了的第八题 还差两道题就编完了的第九题 太好啦终于编完了 为啥一周六天早八阿 瞎编乱造第一题…...

什么是语法糖?Java中有哪些语法糖?
本文从 Java 编译原理角度,深入字节码及 class 文件,抽丝剥茧,了解 Java 中的语法糖原理及用法,帮助大家在学会如何使用 Java 语法糖的同时,了解这些语法糖背后的原理1 语法糖语法糖(Syntactic Sugar&#…...

STM32学习(五)
GPIO General Purpose Input Output,通用输入输出端口,简称GPIO。 作用: 采集外部器件的信息(输入)控制外部器件的工作(输出) GPIO特点 1,不同型号,IO口数量可能不一样…...

STM32的CAN总线调试经验分享
相关文章 CAN总线简易入门教程 CAN总线显性电平和隐性电平详解 STM32的CAN总线调试经验分享 文章目录相关文章背景CAN总线CAN控制器CAN收发器调试过程硬件排查CAN分析仪芯片CAN控制器调试总结背景 最近负责的一个项目用的主控芯片是STM32F407IGT6,需要和几个电机控…...

深度剖析自定义类型(结构体、枚举、联合)——“C”
各位CSDN的uu们你们好呀,今天,小雅兰的内容是心心念念的结构体啦,其实在此之前,我也写过结构体的知识点,只是并没有很深入,那么,今天我会仔细来学习自定义类型的知识点,下面…...

《水经注地图服务》发布的全球影像数据在水经微图中调用
(本文首发于“水经注GIS”公号,订阅“水经注GIS”公号,为你分享更多GIS技术 )1、引言古人云:“工欲善其事,必先利其器。”意思是说:工匠想要使他的工作做好,一定要先让工具锋利&…...

MyBatis --- 缓存、逆向工程、分页插件
一、MyBatis的缓存 1.1、MyBatis的一级缓存 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问 使一级缓存失效的四种情况: 1、…...

vue3自定义svg图标组件
可参考: 未来必热:SVG Sprites技术介绍 懒人神器:svg-sprite-loader实现自己的Icon组件 在Vue3项目中使用svg-sprite-loader 前置知识 在页面中,虽然可以通过如下的方式使用img标签,来引入svg图标。但是,…...

智能火焰与烟雾检测系统(Python+YOLOv5深度学习模型+清新界面)
摘要:智能火焰与烟雾检测系统用于智能日常火灾检测报警,利用摄像头画面实时识别火焰与烟雾,另外支持图片、视频火焰检测并进行结果可视化。本文详细介绍基于智能火焰与烟雾检测系统,在介绍算法原理的同时,给出Python的…...

Java实习生------JUC并发编程(多线程)10道面试题打卡⭐⭐⭐
目录 并行和并发有什么区别? 线程和进程有什么区别? 创建线程有哪几种方式? runnable和callable有什么区别? 线程的状态及转换? sleep()和wait()的区别? run()和start()有什么区别? 在…...

ChatGPT和百度文心一言写用例,谁更强?
文心一言发布的第一时间,就排队申请了邀请码,昨晚看了下,邀请码已经到手,索性就拿一个例子试了一下,看看哪个能够真正意义上的提高生产力,最简单的录制了个GIF动画如下:问题:你是一个…...

设计模式总结
设计模式的六大原则 开放-封闭原则(OCP) (总原则) Open-Close Principle:该对扩展开放,对修改关闭。 目的就是保证程序的扩展性好,易于维护和升级。 开放-封闭原则是面向对象设计的核心所在, 开闭原则是Java世界里最基础的设计原则。 开闭…...

【K8S系列】深入解析Pod对象(一)
目录 序言 1.问题引入 1.1 问题描述 2 问题解答 2.1 pod 属性 2.1.1 NodeSelector 2.1.2 HostAliases 2.1.3 shareProcessNamespace 2.1.4 NodeName 2.1.5 其他pod属性 2.2 容器属性 2.2.1 ImagePullPolicy 2.2.2 Lifecycle 3 总结 4. 投票 序言 任何一件事情&am…...

JVM学习.02 内存分配和回收策略
1、前言《JVM学习.01 内存模型》篇讲述了JVM的内存布局,其中每个区域是作用,以及创建实例对象的时候内存区域的工作流程。上文还讲到了关于对象存货后,会被回收清理的过程。今天这里就着重讲一下对象实例是如何被清理回收的,以及清…...

logstash+elasticsearch+Kibana(ELK)日志收集
文章目录一.安装ELK 7.17二.为Elasticsearch设置密码三.配置logstash四.springboot整合logstash五.spring整合Elastic Search一.安装ELK 7.17 不要一股脑执行以下语句,请观察修改要修改的地方 安装logstash # logstash安装docker run -d --name logstash \-p 5043:5043 -p 5…...

今天面试了一个2年Java经验的
今天去面试了一个26岁的程序员,看了简历,2年经验,本科,写得很牛叉。 Spring cloud alibaba全家桶、redis,分布式锁,服务调用,数据库事务,线程,Zookeeper、Dubbo 、Rabbi…...

逻辑覆盖测试用例设计
逻辑覆盖测试用例设计 实验目标 能够依据程序画出程序流程图理解常用覆盖方法的内涵理解常用覆盖方法的强弱关系能够使用常用覆盖方法设计测试用例 背景知识 白盒测试通常采用静态测试方法和动态测试方法开展。动态测试是参照系统需求或测试规则,通过预先设计一…...