直面ES6中的Proxy和Reflect,发现很简单

        ES6对于今天来说,已经不算是一个很新的概念。从2015年第一版ES6发版之后,每一年都有新的版本产生,新版本是该年正式版本的语言标准。因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等。

        还有一点就是虽然我们现在也会说ES7、ES8这种,但实际上这种说法是不严谨的,因为ES官方自2016年开始,就不再以ES+版本号这种命名了。相反,这些版本按照年份来命名,例如ECMAScript 2016和ECMAScript 2017。这种命名方式更为准确和清晰,能够直接反映出版本的发布年份。

        其中Proxy和Reflect已经是ES2015第一版中出现的了,可见已经算是年代久远了。现在才想到看它实属惭愧。这其实也跟我们日常业务开发用到不多有关,但是正所谓脑子里没有,平常怎么能想到使用呢,因此我们还是有必要直面它的。废话不多说了,开始看一下它们到底是个啥。


Proxy是什么

MDN官方对于proxy的解释:

        Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

阮一峰ES6入门教程中对proxy的解释:

        Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

        Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

如果看完概念还不明白的话,那么我们举个场景例子:

        有一个对象object,其中有一个age参数,我们希望age的范围始终要大于18。那么我们则需要在每次更新age的时候都去增加一个判断。先不说每次更新都这样判断是否麻烦,但凡有一个地方没加判断,age就有可能设置成小于18的数了。

        因此我们就可以给object增加一个代理,让它在我们每次更新object中age的时候,对其进行一个判断,这样我们就只需要关注业务逻辑的更新,而不需要再去增加一些边界条件的判断,使得代码逻辑更清晰更灵活,相信这也是proxy设计的初衷。

        知道了概念之后,我们还需要明白,它能对哪些操作进行拦截呢,又是怎么拦截的呢?

Proxy能对哪些操作拦截

我们日常接触较多的操作

        1、对一个对象属性的修改。例如 object.age = 18 或 object['age'] = 18

        2、获取一个对象的属性。例如object.age 或 object['age']

        3、删除一个对象的属性。例如 delete object.age 或 delete object['age']

        4、查看一个对象中是否存在某个属性。例如 'age' in object

        5、获取对象自身所有属性。例如 Object.keys(obj) 或Object.getOwnPropertyNames(obj)

        6、对一个函数进行调用。例如 fn()

        7、new一个构造函数。例如 let obj = new Object()

还有一些日常接触不多的操作

        8、给对象定义或修改某个属性的描述。如下所示:

let obj = {}
Object.defineProperty(obj, 'age', {  value: 18,  writable: true,  enumerable: true,  configurable: true
});  // 或者defineProperties。
Object.defineProperties(obj, {  'property1': {  value: true,  writable: true  },  'property2': {  value: 'Hello',  writable: false  }  // ... 可以定义更多属性  
});

题外话:

        defineProperty和defineProperties这二者区别就是前者一次只能对一个属性操作,后者一次则能操作多个属性。

        一个属性的数据描述符有四个

                 value(属性值)、

                writable(是否可写)、

                enumerable(是否可枚举)、

                configurable(是否可配置)。

        value没写或者说未明确指定的话,为undefined。其余三个未明确指定的话默认为false


        属性描述符除了上述数据描述符之外,还有存取描述符

                get(): 访问属性时会调用此函数、

                set():修改该属性时会调用此函数、

                enumerable(是否可枚举)、

                configurable(是否可配置)

        数据描述符和存取描述符是互斥的,不能同时在一个属性上设置两者,否则会抛出异常

        9、得到某个对象中某个属性的具体描述。如下所示:

const object = { age: 18 };
Object.getOwnPropertyDescriptor(object, 'age');
// {value: 1, writable: true, enumerable: true, configurable: true}

        10、设置一个对象不可扩展。如下所示:

let myObject = {};
Object.preventExtensions(myObject); // 将myObject设置过不可扩展
myObject.name = '张三';   // 无效
console.log(myObject) // {}

        11、判断一个对象是否可扩展。如下所示:

const myObject = {};
Object.isExtensible(myObject) // true

        12、设置一个目标对象的原型。如下所示:

const myObj = {};
Object.setPrototypeOf(myObj, Array.prototype);

        13、获取一个对象的原型。如下所示:

let p = {};
Object.getPrototypeOf(p)
// 或者
p.__proto__;
// 或者
p instanceof Object;

        以上所述的13种操作,都可以通过Proxy进行拦截。

Proxy怎么拦截呢

        我们知道了proxy能够对哪些操作进行拦截后,然后再来看一下如何通过proxy对这些操作进行拦截。

        ES6原生提供了一个Proxy构造函数,用来生成Proxy实例。

let proxy = new Proxy(target, handler);

        所有拦截方式都是通过上面这种形式来操作的。Proxy构造函数接收两个参数,target表示要拦截的目标对象,handler参数也是一个对象,通过在handler中定义不同方法来进行不同拦截。

        假设我们需要对一个获取属性的操作进行拦截。则可以在handle对象中定义一个get方法,get方法有三个参数,依次为目标对象、属性名、proxy实例本身(可选)。如下所示:

let obj = { age: 20 };
let handle = {get(target, propKey, receiver) {return 18;// return target[propKey];}
}
let proxyObj = new Proxy(obj, handle);
console.log(proxyObj.name); // 18
console.log(proxyObj.sex); // 18
console.log(proxyObj.age); // 18//----------------------------------------------------------------
// 我们可以将handle对象,直接写到Proxy第二个参数里,更简洁一些
let proxyObj = new Proxy(obj, {get(target, propKey, receiver) {return 18;// return target[propKey];}
});

        上述代码,我们通过set方法拦截到了获取属性的操作,并且返回18,这时候我们不管获取什么属性,都会给我们返回18。注意,要想代理起作用,我们必须操作Proxy实例,也就是上述例子中的proxyObj,而不是针对目标对象。

       如果我们需要返回实际属性值的话,只需要return target[propKey]即可。

        其余操作也都有相对应的方法,同理,只需要在proxy的第二个参数里添加相应方法,即可对对象或函数的某种操作进行拦截,这里不再演示。

Proxy所有拦截操作的方法名

        1、set(target, propKey, value, receiver):拦截目标对象的修改。方法有四个参数,依次为所拦截目标对象、待设置的属性名、待设置的属性值、一般为proxy实例。

        2、get(target, propKey, receiver):拦截目标对象属性的读取。方法有三个参数,依次为所拦截目标对象、待获取的属性名、proxy实例。

        3、deleteProperty(target, propKey):拦截目标对象属性的删除。方法有两个参数,依次为所拦截目标对象、待删除的属性名。

        4、has(target, propKey):拦截检查目标对象中是否存在某个属性的操作。方法有两个参数,依次为拦截目标,待检查的属性名。

        5、ownKeys(target):拦截获取目标对象所有属性的操作。方法有一个参数,就是拦截目标。

        6、apply(target, object, args):拦截对目标函数的调用。方法有三个参数,依次为所拦截目标函数,目标的上下文对象(this)、被调用函数的参数数组。

        7、construct(target, args, newTarget):拦截对目标构造函数的new操作。方法有三个参数,依次为目标构造函数、构造函数的参数数组、一般为proxy实例。

        8、defineProperty(target, propKey, propDesc):拦截对目标对象属性描述的操作。方法有三个参数,依次为目标对象,目标对象的属性名、待定义或修改的属性的描述符。

        9、getOwnPropertyDescriptor(target, propKey):拦截获取目标对象属性描述符的操作。方法有两个参数,依次为目标对象、目标对象属性名的描述。

        10、preventExtensions(target):拦截设置对象不可扩展的操作。方法有一个参数,就是目标对象。

        11、isExtensible(target):拦截判断对象是否可扩展的操作。方法就一个参数,就是目标对象。

        12、setPrototypeOf(target, proto):拦截设置目标对象原型的操作。方法有两个参数,依次为目标对象,待设置的新原型或null。

        13、getPrototypeOf(target):拦截获取目标对象原型的操作。方法有一个参数,就是目标对象。

Reflect是什么

        Reflect是一个对象,其中目前有13个方法。包含Object对象中对对象操作的一些方法,以及将一些对象操作的命令变为函数方法(例如 'age' in obj 等同于 Reflect中的has(obj, 'age')方法、还有delete obj[age] 等同于 Relect中的deleteProperty(obj, 'age')方法)

        之所以将Object中的一些方法和操作单独放到一个新的Reflect对象中,有以下几个目的:

        1、一些对对象的操作明显是语言内部的方法,放在Object上可能会有点怪怪的,放在Reflect上会更清晰,无歧义。

        2、优化某些操作,让方法更健壮,结果更合理。

        3、将部分操作变为函数行为,例如上文所讲的in、或delete命令。

        4、未来如果有对象操作的新方法,会只部署在Reflect对象上。

        5、此外相信大家能够发现,Reflect上有13种方法,Rroxy也有13种方法。没错,它们是一一对应的,包括参数(参考上文proxy所有拦截操作的方法名)。因此用Reflect来作为Proxy拦截的默认操作简直太爽太配套。如下所示。

let obj = {};
var proxyObj = new Proxy(obj, {set: function (target, propKey, value, receiver) {console.log(`setting ${propKey}!`);// 当我们不希望影响该操作的结果时,我们只需要使用Reflect中相对于的方法进行默认操作即可return Reflect.set(target, propKey, value, receiver);},get(target, name) {console.log('get', target, name);// return target[name];    // badreturn Reflect.get(target, name); // good},deleteProperty(target, name) {console.log('delete' + name);return Reflect.deleteProperty(target, name);},has(target, name) {console.log('has' + name);return Reflect.has(target, name);}//...});

        最后也建议大家在日常开发过程中用到以上13种操作之一的话,最好使用Reflect中的方法,更好一些。

参考链接:

ES6 入门教程

Proxy - JavaScript | MDN

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

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

相关文章

mysql和Nosql到底有什么区别,分别应用与什么场景?

MySQL 和 NoSQL 是两种不同类型的数据库技术,它们各有其特点和适用场景。了解它们之间的区别和应用场景可以帮助选择合适的技术来支持特定的应用需求。 MySQL MySQL 是一种关系数据库管理系统(RDBMS),它使用结构化查询语言&…

Linux打开html

在 Linux 系统中,您可以使用默认的 Web 浏览器打开 HTML 文件。一般来说,您可以采用以下两种方式打开 HTML 文件: 使用终端命令行打开 HTML 文件 打开终端并进入到 HTML 文件所在目录,然后输入以下命令: xdg-open f…

类和对象(2)——封装(封装的概念、包、staic)

前言 面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节。 一、什么是封装 1.1 概念 将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节&…

ASGI Server之hypercorn

官方文档 简介 产生背景 Hypercorn最初是Quart(一款异步python微框架)的一部分,后来被分离成一个独立的ASGI服务器。Hypercorn从Quart的0.5.0版本分叉。 结构 Hypercorn是一款基于sans-io hyper、h11、h2和wsproto库的ASGI网络服务器,其灵感来自Gun…

零元购与消费增值:电商新商业模式的探索与实践

大家好,我是微三云周丽,今天给大家分析当下市场比较火爆的商业模式! 小编今天跟大伙们分享什么是零元购与消费增值模式? 在数字化浪潮的推动下,电商行业正经历着qian所未有的变革。传统的ying销ce略逐渐失去效力&…

有关栈的练习

栈练习1 给定一个栈(初始为空,元素类型为整数,且小于等于 109),只有两个操作:入栈和出栈。先给出这些操作,请输出最终栈的栈顶元素。 操作解释: 1 表示将一个数据元素入栈&#xff…

webmagic 爬取https的网站抛avax.net.ssl.SSLHandshakeException异常

webmagic 抓取带有https的网站,抛出的异常javax.net.ssl.SSLHandshakeException。 初步解决办法: 1,在自己的项目中新建httpclient文件夹,新建类HttpClientGenerator, 复制webmagic源码中的 HttpClientGenerator. 2.修改 HttpClientGenerator…

Baumer工业相机堡盟工业相机如何通过NEOAPISDK实现相机资源的正确释放(C#)

Baumer工业相机堡盟工业相机如何通过NEOAPISDK实现相机资源的正确释放(C#) Baumer工业相机Baumer工业相机NEOAPI SDK和相机资源释放的技术背景Baumer工业相机通过NEOAPISDK实现相机资源释放功能1.引用合适的类文件2.通过NEOAPISDK实现相机资源释放 Baume…

书生浦语训练营第2期-第5节作业

一、基础作业 1.1 LMDeploy环境部署 (1)创建conda环境 studio-conda -t lmdeploy -o pytorch-2.1.2 (2)安装Lmdeploy 激活刚刚创建的虚拟环境。 conda activate lmdeploy 安装0.3.0版本的lmdeploy。 pip install lmdeploy[all]0…

达梦(DM)数据库表索引

达梦DM数据库表索引 表索引索引准则其他准则 创建索引显式地创建索引其他创建索引语句 使用索引重建索引删除索引 表索引 达梦数据库表索引相关内容比较多,常用的可能也就固定的一些,这里主要说一下常用的索引,从物理存储角度进行分类&#…

傅立叶变换与拉普拉斯变换的区别与联系?

傅里叶变换和拉普拉斯变换都是信号处理中的重要工具,它们有以下几个主要区别: 定义域:傅里叶变换是在频率域(即虚轴)上定义的,而拉普拉斯变换在复平面上的特定区域内定义。 适用范围:傅里叶变换…

在线测径仪的六类测头组合形式!哪种适合你?

在线测径仪,这一现代工业的精密仪器,犹如一位技艺高超的工匠,以其卓越的性能和精准度,为工业生产提供了坚实的保障。它的出现,不仅提高了生产效率,更保证了产品质量,为企业的可持续发展注入了强…

基于JavaWeb开发的springboot网约车智能接单规划小程序[附源码]

基于JavaWeb开发的springboot网约车智能接单规划小程序[附源码] 🍅 作者主页 央顺技术团队 🍅 欢迎点赞 👍 收藏 ⭐留言 📝 🍅 文末获取源码联系方式 📝 🍅 查看下方微信号获取联系方式 承接各种…

SLICEM是如何将查找表配置为分布式RAM/移位寄存器的

1.首先说SliceM和SliceL如何配置为ROM的 一个SLICE包含4个六输入查找表,因此每个查找表就能存储64bit的数据,要实现128bit的ROM,只需要通过两个LUT就可实现,具体如下表: 2.如何配置成为分布式RAM SLICEM中的LUT如下图&#xff…

华为OD机试真题-欢乐的周末-2024年OD统一考试(C卷)

题目描述: 小华和小为是很要好的朋友,他们约定周末一起吃饭。通过手机交流,他们在地图上选择了多个聚餐地点(由于自然地形等原因,部分聚餐地点不可达),求小华和小为都能到达的聚餐地点有多少个? 输入描述: 第一行输入m和n,m代表地图的长度,n代表地图的宽度。 第二行…

Jetpack Compose -> 重组的性能风险和优化

前言 上一章我们讲解了 Jetpack Compose -> mutableStateOf 状态机制的背后秘密 本章我们讲解下重组的性能风险以及怎么优化; 重组的性能风险 前面我们一直在讲重组(ReCompose) 的过程,在使用 mutableStateOf() 以及对于 List 和 Map 在使用 mutatbl…

Excel模板导入、导出工具类

1.引入maven依赖&#xff0c;利用hutool的excel读取 Hutool-poi对excel读取、写入 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version></dependency> <depen…

Linux之安装Nginx

目录 传送门前言一、快速安装二、反向代理语法1、基本语法2、location语法1. 基本语法2. 匹配规则3. 修饰符4. 权重5. 嵌套location6. 其他指令7.案例 三、配置反向代理 传送门 SpringMVC的源码解析&#xff08;精品&#xff09; Spring6的源码解析&#xff08;精品&#xff0…

Java 海报-基于Graphics2D 实现个人头像的圆形裁剪

效果&#xff1a; 代码&#xff1a; private static BufferedImage resizeAndClipToCircle(BufferedImage image, int size) {// 缩小图片BufferedImage resizedImage new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);Graphics2D g2d resizedImage.createGraphi…

5.组合与继承

1.面向对象 在C中&#xff0c;面向对象&#xff08;Object-Oriented&#xff09;是一种程序设计范式&#xff0c;它使用“对象”来设计应用程序和软件。面向对象编程&#xff08;OOP&#xff09;的核心概念包括类&#xff08;Class&#xff09;、对象&#xff08;Object&#x…